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.
@@ -1,7 +1,7 @@
1
- import { fromEvent, Observable, EMPTY, of, Subject, timer, race } from 'rxjs';
1
+ import { fromEvent, Observable, Subject, isObservable, EMPTY, of, timer, race } from 'rxjs';
2
2
  import { takeUntil, map, filter, withLatestFrom, endWith, take, mergeMap, tap } from 'rxjs/operators';
3
3
  import * as i0 from '@angular/core';
4
- import { inject, ApplicationRef, Injector, ViewContainerRef, NgZone, TemplateRef, Injectable } from '@angular/core';
4
+ import { InjectionToken, inject, Injectable, ElementRef, TemplateRef, createComponent, ApplicationRef, Pipe, ChangeDetectorRef, Injector, ViewContainerRef, NgZone } from '@angular/core';
5
5
  import { DOCUMENT } from '@angular/common';
6
6
 
7
7
  const FOCUSABLE_ELEMENTS_SELECTOR = [
@@ -91,6 +91,59 @@ function isInteger(value) {
91
91
  function isDefined(value) {
92
92
  return value !== undefined && value !== null;
93
93
  }
94
+ /**
95
+ * Determines if two objects or values are equivalent.
96
+ *
97
+ * @param o1 Object or value to compare.
98
+ * @param o2 Object or value to compare.
99
+ * @returns true if arguments are equal.
100
+ */
101
+ function equals(o1, o2) {
102
+ if (o1 === o2) {
103
+ return true;
104
+ }
105
+ if (o1 === null || o2 === null) {
106
+ return false;
107
+ }
108
+ if (o1 !== o1 && o2 !== o2) {
109
+ return true;
110
+ } // NaN === NaN
111
+ let t1 = typeof o1, t2 = typeof o2, length, key, keySet;
112
+ if (t1 == t2 && t1 == 'object') {
113
+ if (Array.isArray(o1)) {
114
+ if (!Array.isArray(o2)) {
115
+ return false;
116
+ }
117
+ if ((length = o1.length) == o2.length) {
118
+ for (key = 0; key < length; key++) {
119
+ if (!equals(o1[key], o2[key])) {
120
+ return false;
121
+ }
122
+ }
123
+ return true;
124
+ }
125
+ }
126
+ else {
127
+ if (Array.isArray(o2)) {
128
+ return false;
129
+ }
130
+ keySet = Object.create(null);
131
+ for (key in o1) {
132
+ if (!equals(o1[key], o2[key])) {
133
+ return false;
134
+ }
135
+ keySet[key] = true;
136
+ }
137
+ for (key in o2) {
138
+ if (!(key in keySet) && typeof o2[key] !== 'undefined') {
139
+ return false;
140
+ }
141
+ }
142
+ return true;
143
+ }
144
+ }
145
+ return false;
146
+ }
94
147
  /**
95
148
  * Checks if a value is a Promise.
96
149
  *
@@ -176,6 +229,49 @@ function runInZone(zone) {
176
229
  function removeAccents(str) {
177
230
  return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
178
231
  }
232
+ /**
233
+ * Replaces placeholders in a string with corresponding values from a given object.
234
+ *
235
+ * @param expr a string that represents the expression to be interpolated.
236
+ * @param params an optional object that contains the values to be interpolated into the expr string.
237
+ * @returns the interpolated string.
238
+ */
239
+ function interpolateString(expr = '', params = {}, templateMatcher = /{{\s?([^{}\s]*)\s?}}/g) {
240
+ if (!params) {
241
+ return expr;
242
+ }
243
+ return expr.replace(templateMatcher, (substring, b) => {
244
+ let r = getValue(params, b);
245
+ return isDefined(r) ? r : substring;
246
+ });
247
+ }
248
+ /**
249
+ * Retrieves the value of a nested property from an object using dot notation.
250
+ *
251
+ * @param target the object from which you want to retrieve a value.
252
+ * @param key a string that represents the property or nested properties of the target object.
253
+ * @returns the value of the specified key in the target object.
254
+ */
255
+ function getValue(target, key) {
256
+ let keys = typeof key === 'string' ? key.split('.') : [key];
257
+ key = '';
258
+ do {
259
+ key += keys.shift();
260
+ if (isDefined(target) &&
261
+ isDefined(target[key]) &&
262
+ (typeof target[key] === 'object' || !keys.length)) {
263
+ target = target[key];
264
+ key = '';
265
+ }
266
+ else if (!keys.length) {
267
+ target = undefined;
268
+ }
269
+ else {
270
+ key += '.';
271
+ }
272
+ } while (keys.length);
273
+ return target;
274
+ }
179
275
  /**
180
276
  * Returns the active element in the given root.
181
277
  *
@@ -191,6 +287,679 @@ function getActiveElement(root = document) {
191
287
  : activeEl;
192
288
  }
193
289
 
290
+ const HUB_TRANSLATION_CONFIG = new InjectionToken('HUB_TRANSLATION_CONFIG');
291
+
292
+ class HubTranslationService {
293
+ #config = inject(HUB_TRANSLATION_CONFIG, { optional: true }) ?? {};
294
+ defaultTranslations = this.#config.dictionaries ?? {};
295
+ translations;
296
+ translationSource = new Subject();
297
+ translationObserver = this.translationSource.asObservable();
298
+ constructor() {
299
+ this.initialize();
300
+ }
301
+ initialize() {
302
+ const language = this.#config.language ?? this.#config.fallbackLanguage ?? 'en';
303
+ const fallbackLanguage = this.#config.fallbackLanguage ?? 'en';
304
+ const fallbackTranslations = this.defaultTranslations[fallbackLanguage] ?? {};
305
+ const selectedTranslations = this.defaultTranslations[language] ?? fallbackTranslations;
306
+ this.setTranslations(selectedTranslations);
307
+ }
308
+ /**
309
+ * Retrieves a value from a translations object based on a given key.
310
+ */
311
+ getTranslation(key) {
312
+ return getValue(this.translations, key);
313
+ }
314
+ /**
315
+ * Merges fallback translations with the provided translations and updates observers.
316
+ */
317
+ setTranslations(translations = {}) {
318
+ const fallbackLanguage = this.#config.fallbackLanguage ?? 'en';
319
+ const fallbackTranslations = this.defaultTranslations[fallbackLanguage] ?? {};
320
+ const nextTranslations = translations ?? {};
321
+ this.translations = { ...fallbackTranslations, ...nextTranslations };
322
+ this.translationSource.next(this.translations);
323
+ }
324
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HubTranslationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
325
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HubTranslationService });
326
+ }
327
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HubTranslationService, decorators: [{
328
+ type: Injectable
329
+ }], ctorParameters: () => [] });
330
+
331
+ /**
332
+ * Positions an overlay container relative to an origin element.
333
+ * The first configured position that fits within the viewport is applied.
334
+ */
335
+ class OverlayPosition {
336
+ _origin = null;
337
+ _positions = [];
338
+ /**
339
+ * Sets the origin element used to position the overlay.
340
+ *
341
+ * @param origin Element reference or HTMLElement.
342
+ * @returns This position instance for chaining.
343
+ */
344
+ flexibleConnectedTo(origin) {
345
+ this._origin = origin;
346
+ return this;
347
+ }
348
+ /**
349
+ * Sets the preferred positions for the overlay.
350
+ * The order of the array determines the fallback priority.
351
+ *
352
+ * @param positions Array of position configurations.
353
+ * @returns This position instance for chaining.
354
+ */
355
+ withPositions(positions) {
356
+ this._positions = positions;
357
+ return this;
358
+ }
359
+ /**
360
+ * Applies the calculated position to the overlay element.
361
+ *
362
+ * @param overlayElement The overlay container element.
363
+ */
364
+ apply(overlayElement) {
365
+ if (!this._origin) {
366
+ return;
367
+ }
368
+ const originElement = this._origin instanceof ElementRef ? this._origin.nativeElement : this._origin;
369
+ const originRect = originElement.getBoundingClientRect();
370
+ // Try each position until we find one that fits in the viewport
371
+ for (const position of this._positions) {
372
+ const coords = this._calculatePosition(originRect, overlayElement, position);
373
+ if (this._fitsInViewport(coords, overlayElement)) {
374
+ this._applyPosition(overlayElement, coords);
375
+ return;
376
+ }
377
+ }
378
+ // If no position fits perfectly, use the first one
379
+ if (this._positions.length > 0) {
380
+ const coords = this._calculatePosition(originRect, overlayElement, this._positions[0]);
381
+ this._applyPosition(overlayElement, coords);
382
+ }
383
+ }
384
+ /**
385
+ * Calculates the position coordinates based on the configuration.
386
+ *
387
+ * @param originRect Bounding rectangle of the origin element.
388
+ * @param overlayElement The overlay element.
389
+ * @param position Position configuration.
390
+ * @returns Calculated x and y coordinates.
391
+ */
392
+ _calculatePosition(originRect, overlayElement, position) {
393
+ const overlayRect = overlayElement.getBoundingClientRect();
394
+ // Calculate origin point
395
+ let x = this._getOriginX(originRect, position.originX);
396
+ let y = this._getOriginY(originRect, position.originY);
397
+ // Adjust for overlay alignment
398
+ x -= this._getOverlayX(overlayRect, position.overlayX);
399
+ y -= this._getOverlayY(overlayRect, position.overlayY);
400
+ // Apply offsets
401
+ if (position.offsetX) {
402
+ x += position.offsetX;
403
+ }
404
+ if (position.offsetY) {
405
+ y += position.offsetY;
406
+ }
407
+ return { x, y };
408
+ }
409
+ /**
410
+ * Gets the X coordinate for the origin point.
411
+ */
412
+ _getOriginX(rect, position) {
413
+ switch (position) {
414
+ case 'start':
415
+ return rect.left;
416
+ case 'center':
417
+ return rect.left + rect.width / 2;
418
+ case 'end':
419
+ return rect.right;
420
+ }
421
+ }
422
+ /**
423
+ * Gets the Y coordinate for the origin point.
424
+ */
425
+ _getOriginY(rect, position) {
426
+ switch (position) {
427
+ case 'top':
428
+ return rect.top;
429
+ case 'center':
430
+ return rect.top + rect.height / 2;
431
+ case 'bottom':
432
+ return rect.bottom;
433
+ }
434
+ }
435
+ /**
436
+ * Gets the X offset for the overlay alignment.
437
+ */
438
+ _getOverlayX(rect, position) {
439
+ switch (position) {
440
+ case 'start':
441
+ return 0;
442
+ case 'center':
443
+ return rect.width / 2;
444
+ case 'end':
445
+ return rect.width;
446
+ }
447
+ }
448
+ /**
449
+ * Gets the Y offset for the overlay alignment.
450
+ */
451
+ _getOverlayY(rect, position) {
452
+ switch (position) {
453
+ case 'top':
454
+ return 0;
455
+ case 'center':
456
+ return rect.height / 2;
457
+ case 'bottom':
458
+ return rect.height;
459
+ }
460
+ }
461
+ /**
462
+ * Checks if the overlay fits within the viewport at the given coordinates.
463
+ */
464
+ _fitsInViewport(coords, overlayElement) {
465
+ const overlayRect = overlayElement.getBoundingClientRect();
466
+ const viewportWidth = window.innerWidth;
467
+ const viewportHeight = window.innerHeight;
468
+ return (coords.x >= 0 &&
469
+ coords.y >= 0 &&
470
+ coords.x + overlayRect.width <= viewportWidth &&
471
+ coords.y + overlayRect.height <= viewportHeight);
472
+ }
473
+ /**
474
+ * Applies the calculated position to the overlay element.
475
+ */
476
+ _applyPosition(overlayElement, coords) {
477
+ overlayElement.style.left = `${coords.x}px`;
478
+ overlayElement.style.top = `${coords.y}px`;
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Manages a single overlay instance created by {@link OverlayService}.
484
+ * Creates a container and optional backdrop in `document.body` and attaches
485
+ * either a template or component as the overlay content.
486
+ */
487
+ class OverlayRef {
488
+ _config;
489
+ _appRef;
490
+ _backdropElement = null;
491
+ _containerElement = null;
492
+ _contentElement = null;
493
+ _viewRef = null;
494
+ _componentRef = null;
495
+ _isAttached = false;
496
+ _backdropClickCallback;
497
+ _backdropClickHandler;
498
+ constructor(_config, _appRef) {
499
+ this._config = _config;
500
+ this._appRef = _appRef;
501
+ }
502
+ /**
503
+ * Attaches content to the overlay.
504
+ *
505
+ * If the overlay is already attached, it only updates the position strategy
506
+ * and returns the existing content element.
507
+ *
508
+ * @param content Template or component type to attach.
509
+ * @param viewContainerRef View container used to create embedded views for templates.
510
+ * @returns The attached content element (first root node).
511
+ * @throws When a {@link TemplateRef} is provided without a {@link ViewContainerRef}.
512
+ */
513
+ attach(content, viewContainerRef) {
514
+ // If already attached, just update position and return existing element
515
+ if (this._isAttached && this._contentElement) {
516
+ if (this._config.positionStrategy) {
517
+ this._config.positionStrategy.apply(this._containerElement);
518
+ }
519
+ return this._contentElement;
520
+ }
521
+ this._createContainer();
522
+ this._createBackdrop();
523
+ let contentElement;
524
+ if (content instanceof TemplateRef) {
525
+ if (!viewContainerRef) {
526
+ throw new Error('ViewContainerRef is required when attaching a TemplateRef');
527
+ }
528
+ // Only create and attach view if not already created
529
+ if (!this._viewRef) {
530
+ this._viewRef = viewContainerRef.createEmbeddedView(content);
531
+ this._viewRef.detectChanges();
532
+ }
533
+ contentElement = this._viewRef.rootNodes[0];
534
+ }
535
+ else {
536
+ if (this._componentRef) {
537
+ this._appRef.detachView(this._componentRef.hostView);
538
+ this._componentRef.destroy();
539
+ }
540
+ this._componentRef = createComponent(content, {
541
+ environmentInjector: this._appRef.injector
542
+ });
543
+ this._appRef.attachView(this._componentRef.hostView);
544
+ contentElement = this._componentRef.hostView
545
+ .rootNodes[0];
546
+ }
547
+ this._contentElement = contentElement;
548
+ // Only append if not already in container
549
+ if (!this._containerElement.contains(contentElement)) {
550
+ this._containerElement.appendChild(contentElement);
551
+ }
552
+ this._isAttached = true;
553
+ // Apply position strategy
554
+ if (this._config.positionStrategy) {
555
+ this._config.positionStrategy.apply(this._containerElement);
556
+ }
557
+ return contentElement;
558
+ }
559
+ /**
560
+ * Detaches the content from the overlay container without disposing the overlay.
561
+ */
562
+ detach() {
563
+ if (!this._isAttached) {
564
+ return;
565
+ }
566
+ if (this._contentElement && this._containerElement) {
567
+ this._containerElement.removeChild(this._contentElement);
568
+ }
569
+ this._isAttached = false;
570
+ }
571
+ /**
572
+ * Disposes the overlay and cleans up all allocated resources.
573
+ */
574
+ dispose() {
575
+ this.detach();
576
+ // Destroy view ref if exists
577
+ if (this._viewRef) {
578
+ this._viewRef.destroy();
579
+ this._viewRef = null;
580
+ }
581
+ if (this._componentRef) {
582
+ this._appRef.detachView(this._componentRef.hostView);
583
+ this._componentRef.destroy();
584
+ this._componentRef = null;
585
+ }
586
+ if (this._containerElement) {
587
+ document.body.removeChild(this._containerElement);
588
+ this._containerElement = null;
589
+ }
590
+ if (this._backdropElement) {
591
+ // Remove event listener before removing element from DOM
592
+ if (this._backdropClickHandler) {
593
+ this._backdropElement.removeEventListener('click', this._backdropClickHandler);
594
+ this._backdropClickHandler = undefined;
595
+ }
596
+ document.body.removeChild(this._backdropElement);
597
+ this._backdropElement = null;
598
+ }
599
+ this._contentElement = null;
600
+ this._backdropClickCallback = undefined;
601
+ }
602
+ /**
603
+ * Checks whether content is currently attached to the overlay.
604
+ */
605
+ hasAttached() {
606
+ return this._isAttached;
607
+ }
608
+ /**
609
+ * Registers a callback for backdrop clicks.
610
+ * The last registered callback replaces any previous one.
611
+ *
612
+ * @param callback Function to call when the backdrop is clicked.
613
+ */
614
+ onBackdropClick(callback) {
615
+ this._backdropClickCallback = callback;
616
+ }
617
+ /**
618
+ * Re-applies the configured position strategy to the overlay container.
619
+ */
620
+ updatePosition() {
621
+ if (this._config.positionStrategy && this._containerElement) {
622
+ this._config.positionStrategy.apply(this._containerElement);
623
+ }
624
+ }
625
+ /**
626
+ * Creates the overlay container element and appends it to the document.
627
+ */
628
+ _createContainer() {
629
+ if (this._containerElement) {
630
+ return;
631
+ }
632
+ this._containerElement = document.createElement('div');
633
+ this._containerElement.classList.add('hub-overlay-container');
634
+ if (this._config.panelClass) {
635
+ const classes = Array.isArray(this._config.panelClass)
636
+ ? this._config.panelClass
637
+ : [this._config.panelClass];
638
+ classes.forEach((cls) => this._containerElement.classList.add(cls));
639
+ }
640
+ if (this._config.width) {
641
+ this._containerElement.style.width =
642
+ typeof this._config.width === 'number'
643
+ ? `${this._config.width}px`
644
+ : this._config.width;
645
+ }
646
+ if (this._config.height) {
647
+ this._containerElement.style.height =
648
+ typeof this._config.height === 'number'
649
+ ? `${this._config.height}px`
650
+ : this._config.height;
651
+ }
652
+ this._containerElement.style.position = 'fixed';
653
+ this._containerElement.style.zIndex = '1000';
654
+ document.body.appendChild(this._containerElement);
655
+ }
656
+ /**
657
+ * Creates the backdrop element if enabled and appends it to the document.
658
+ */
659
+ _createBackdrop() {
660
+ if (!this._config.hasBackdrop || this._backdropElement) {
661
+ return;
662
+ }
663
+ this._backdropElement = document.createElement('div');
664
+ this._backdropElement.classList.add('hub-overlay-backdrop');
665
+ if (this._config.backdropClass) {
666
+ this._backdropElement.classList.add(this._config.backdropClass);
667
+ }
668
+ this._backdropElement.style.position = 'fixed';
669
+ this._backdropElement.style.top = '0';
670
+ this._backdropElement.style.left = '0';
671
+ this._backdropElement.style.width = '100%';
672
+ this._backdropElement.style.height = '100%';
673
+ this._backdropElement.style.zIndex = '999';
674
+ // Store reference to the handler for cleanup
675
+ this._backdropClickHandler = () => {
676
+ if (this._backdropClickCallback) {
677
+ this._backdropClickCallback();
678
+ }
679
+ };
680
+ this._backdropElement.addEventListener('click', this._backdropClickHandler);
681
+ document.body.appendChild(this._backdropElement);
682
+ }
683
+ }
684
+
685
+ /**
686
+ * Service for creating and managing overlay instances.
687
+ */
688
+ class OverlayService {
689
+ _appRef = inject(ApplicationRef);
690
+ /**
691
+ * Creates a new overlay with the specified configuration.
692
+ *
693
+ * @param config Configuration options for the overlay.
694
+ * @returns A reference to the created overlay.
695
+ */
696
+ create(config = {}) {
697
+ return new OverlayRef(config, this._appRef);
698
+ }
699
+ /**
700
+ * Creates a position strategy builder for connected overlays.
701
+ *
702
+ * @returns A new {@link OverlayPosition} instance.
703
+ */
704
+ position() {
705
+ return new OverlayPosition();
706
+ }
707
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: OverlayService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
708
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: OverlayService, providedIn: 'root' });
709
+ }
710
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: OverlayService, decorators: [{
711
+ type: Injectable,
712
+ args: [{
713
+ providedIn: 'root'
714
+ }]
715
+ }] });
716
+
717
+ class GetPipe {
718
+ /**
719
+ * @param value The object to retrieve the property from.
720
+ * @param path The dot-separated path string to the property.
721
+ * @param defaultValue The value to return if the property is not found.
722
+ */
723
+ transform(value, path, defaultValue) {
724
+ if (typeof path !== 'string') {
725
+ return value;
726
+ }
727
+ return path
728
+ .split('.')
729
+ .reduce((a, c) => a && a[c] !== null && a[c] !== undefined
730
+ ? a[c]
731
+ : defaultValue || null, value);
732
+ }
733
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: GetPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
734
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.3", ngImport: i0, type: GetPipe, isStandalone: true, name: "get" });
735
+ }
736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: GetPipe, decorators: [{
737
+ type: Pipe,
738
+ args: [{
739
+ name: 'get',
740
+ standalone: true
741
+ }]
742
+ }] });
743
+
744
+ class IsObjectPipe {
745
+ transform(value) {
746
+ return typeof value === 'object';
747
+ }
748
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: IsObjectPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
749
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.3", ngImport: i0, type: IsObjectPipe, isStandalone: true, name: "isObject" });
750
+ }
751
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: IsObjectPipe, decorators: [{
752
+ type: Pipe,
753
+ args: [{
754
+ name: 'isObject'
755
+ }]
756
+ }] });
757
+
758
+ class IsObservablePipe {
759
+ transform(value) {
760
+ return isObservable(value);
761
+ }
762
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: IsObservablePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
763
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.3", ngImport: i0, type: IsObservablePipe, isStandalone: true, name: "isObservable" });
764
+ }
765
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: IsObservablePipe, decorators: [{
766
+ type: Pipe,
767
+ args: [{
768
+ name: 'isObservable',
769
+ standalone: true
770
+ }]
771
+ }] });
772
+
773
+ class IsStringPipe {
774
+ transform(value) {
775
+ return typeof value === 'string';
776
+ }
777
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: IsStringPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
778
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.3", ngImport: i0, type: IsStringPipe, isStandalone: true, name: "isString" });
779
+ }
780
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: IsStringPipe, decorators: [{
781
+ type: Pipe,
782
+ args: [{
783
+ name: 'isString'
784
+ }]
785
+ }] });
786
+
787
+ class TranslatePipe {
788
+ _ref = inject(ChangeDetectorRef);
789
+ _translationSvc = inject(HubTranslationService);
790
+ value = '';
791
+ lastKey = null;
792
+ lastParams = [];
793
+ translationSubscription;
794
+ /**
795
+ * Updates the value of a key by interpolating the translation and marking for change detection.
796
+ */
797
+ updateValue(key, interpolateParams) {
798
+ const value = interpolateString(this._translationSvc.getTranslation(key), interpolateParams);
799
+ this.value = value !== undefined ? value : key;
800
+ this.lastKey = key;
801
+ this._ref.markForCheck();
802
+ }
803
+ /**
804
+ * Transforms a translation key with optional interpolation params.
805
+ */
806
+ transform(query, ...args) {
807
+ if (!query || !query.length) {
808
+ return query;
809
+ }
810
+ // If we ask another time for the same key, return the last value.
811
+ if (equals(query, this.lastKey) && equals(args, this.lastParams)) {
812
+ return this.value;
813
+ }
814
+ let interpolateParams = undefined;
815
+ if (isDefined(args[0]) && args.length) {
816
+ if (typeof args[0] === 'string' && args[0].length) {
817
+ // We accept objects written in the template such as {n:1}, {'n':1}, {n:'v'}.
818
+ // This converts them to valid JSON.
819
+ let validArgs = args[0]
820
+ .replace(/(\')?([a-zA-Z0-9_]+)(\')?(\s)?:/g, '"$2":')
821
+ .replace(/:(\s)?(\')(.*?)(\')/g, ':"$3"');
822
+ try {
823
+ interpolateParams = JSON.parse(validArgs);
824
+ }
825
+ catch (e) {
826
+ throw new SyntaxError(`Wrong parameter in TranslatePipe. Expected a valid Object, received: ${args[0]}`);
827
+ }
828
+ }
829
+ else if (typeof args[0] === 'object' && !Array.isArray(args[0])) {
830
+ interpolateParams = args[0];
831
+ }
832
+ }
833
+ // Store the query, in case it changes.
834
+ this.lastKey = query;
835
+ // Store the params, in case they change.
836
+ this.lastParams = args;
837
+ // Set the value.
838
+ this.updateValue(query, interpolateParams);
839
+ // Clean any existing subscription.
840
+ this._dispose();
841
+ if (!this.translationSubscription) {
842
+ this.translationSubscription =
843
+ this._translationSvc.translationObserver.subscribe(() => {
844
+ if (this.lastKey) {
845
+ this.lastKey = null;
846
+ this.updateValue(query, interpolateParams);
847
+ }
848
+ });
849
+ }
850
+ return this.value;
851
+ }
852
+ /**
853
+ * Clean any existing subscription to change events.
854
+ */
855
+ _dispose() {
856
+ if (typeof this.translationSubscription !== 'undefined') {
857
+ this.translationSubscription.unsubscribe();
858
+ this.translationSubscription = undefined;
859
+ }
860
+ }
861
+ ngOnDestroy() {
862
+ this._dispose();
863
+ }
864
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: TranslatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
865
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.3", ngImport: i0, type: TranslatePipe, isStandalone: true, name: "translate", pure: false });
866
+ }
867
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: TranslatePipe, decorators: [{
868
+ type: Pipe,
869
+ args: [{
870
+ name: 'translate',
871
+ standalone: true,
872
+ pure: false
873
+ }]
874
+ }] });
875
+
876
+ class UcfirstPipe {
877
+ transform(value = '') {
878
+ return value.charAt(0).toUpperCase() + value.slice(1);
879
+ }
880
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: UcfirstPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
881
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.3", ngImport: i0, type: UcfirstPipe, isStandalone: true, name: "ucfirst" });
882
+ }
883
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: UcfirstPipe, decorators: [{
884
+ type: Pipe,
885
+ args: [{
886
+ name: 'ucfirst',
887
+ standalone: true
888
+ }]
889
+ }] });
890
+
891
+ /**
892
+ * A standalone pipe that unwraps the value of an observable or returns the value directly if it's not an observable.
893
+ *
894
+ * @description
895
+ * The `UnwrapAsyncPipe` is used to unwrap the value emitted by an observable or return the value directly if it's not an observable.
896
+ * It subscribes to the observable and returns the emitted value. If the input is not an observable, it simply returns the value.
897
+ *
898
+ * @usageNotes
899
+ * ```html
900
+ * <div>{{ observableOrValue | unwrapAsync }}</div>
901
+ * ```
902
+ *
903
+ * @publicApi
904
+ */
905
+ class UnwrapAsyncPipe {
906
+ #cdr = inject(ChangeDetectorRef);
907
+ /**
908
+ * The unwrapped value of the observable or the direct value.
909
+ */
910
+ value = null;
911
+ /**
912
+ * The subscription to the observable.
913
+ */
914
+ subscription = null;
915
+ /**
916
+ * Performs cleanup tasks when the pipe is destroyed.
917
+ */
918
+ ngOnDestroy() {
919
+ this.unsubscribe();
920
+ }
921
+ /**
922
+ * Transforms the input value.
923
+ *
924
+ * @param value The input value to transform. It can be an observable or a direct value.
925
+ * @returns The unwrapped value of the observable or the direct value.
926
+ */
927
+ transform(value) {
928
+ if (value instanceof Observable) {
929
+ this.unsubscribe();
930
+ this.subscription = value.subscribe((result) => {
931
+ this.value = result;
932
+ this.#cdr.markForCheck();
933
+ });
934
+ }
935
+ else {
936
+ // Clean up subscription when switching to direct value
937
+ this.unsubscribe();
938
+ this.value = value;
939
+ }
940
+ return this.value;
941
+ }
942
+ /**
943
+ * Unsubscribes from the current subscription.
944
+ */
945
+ unsubscribe() {
946
+ if (this.subscription) {
947
+ this.subscription.unsubscribe();
948
+ this.subscription = null;
949
+ }
950
+ }
951
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: UnwrapAsyncPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
952
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.3", ngImport: i0, type: UnwrapAsyncPipe, isStandalone: true, name: "unwrapAsync", pure: false });
953
+ }
954
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: UnwrapAsyncPipe, decorators: [{
955
+ type: Pipe,
956
+ args: [{
957
+ name: 'unwrapAsync',
958
+ standalone: true,
959
+ pure: false
960
+ }]
961
+ }] });
962
+
194
963
  function getTransitionDurationMs(element) {
195
964
  const { transitionDelay, transitionDuration } = window.getComputedStyle(element);
196
965
  const transitionDelaySec = parseFloat(transitionDelay);
@@ -272,6 +1041,9 @@ const hubCompleteTransition = (element) => {
272
1041
  };
273
1042
 
274
1043
  class ContentRef {
1044
+ nodes;
1045
+ viewRef;
1046
+ componentRef;
275
1047
  constructor(nodes, viewRef, componentRef) {
276
1048
  this.nodes = nodes;
277
1049
  this.viewRef = viewRef;
@@ -279,15 +1051,16 @@ class ContentRef {
279
1051
  }
280
1052
  }
281
1053
  class PopupService {
1054
+ _componentType;
1055
+ _windowRef = null;
1056
+ _contentRef = null;
1057
+ _document = inject(DOCUMENT);
1058
+ _applicationRef = inject(ApplicationRef);
1059
+ _injector = inject(Injector);
1060
+ _viewContainerRef = inject(ViewContainerRef);
1061
+ _ngZone = inject(NgZone);
282
1062
  constructor(_componentType) {
283
1063
  this._componentType = _componentType;
284
- this._windowRef = null;
285
- this._contentRef = null;
286
- this._document = inject(DOCUMENT);
287
- this._applicationRef = inject(ApplicationRef);
288
- this._injector = inject(Injector);
289
- this._viewContainerRef = inject(ViewContainerRef);
290
- this._ngZone = inject(NgZone);
291
1064
  }
292
1065
  open(content, templateContext, animation = false) {
293
1066
  if (!this._windowRef) {
@@ -339,9 +1112,7 @@ class PopupService {
339
1112
  * by adding an equivalent padding on the right of the body, and to revert this change.
340
1113
  */
341
1114
  class ScrollBar {
342
- constructor() {
343
- this._document = inject(DOCUMENT);
344
- }
1115
+ _document = inject(DOCUMENT);
345
1116
  /**
346
1117
  * To be called to hide a potential vertical scrollbar:
347
1118
  * - if a scrollbar is there and has a width greater than 0, adds some compensation
@@ -367,10 +1138,10 @@ class ScrollBar {
367
1138
  bodyStyle.overflow = overflow;
368
1139
  };
369
1140
  }
370
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.5", ngImport: i0, type: ScrollBar, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
371
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.5", ngImport: i0, type: ScrollBar, providedIn: 'root' }); }
1141
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: ScrollBar, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1142
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: ScrollBar, providedIn: 'root' });
372
1143
  }
373
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.5", ngImport: i0, type: ScrollBar, decorators: [{
1144
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: ScrollBar, decorators: [{
374
1145
  type: Injectable,
375
1146
  args: [{ providedIn: 'root' }]
376
1147
  }] });
@@ -383,5 +1154,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.5", ngImpor
383
1154
  * Generated bundle index. Do not edit.
384
1155
  */
385
1156
 
386
- export { ContentRef, FOCUSABLE_ELEMENTS_SELECTOR, PopupService, ScrollBar, closest, getActiveElement, getFocusableBoundaryElements, getValueInRange, hubCompleteTransition, hubFocusTrap, hubRunTransition, isDefined, isInteger, isNumber, isPromise, isString, padNumber, reflow, regExpEscape, removeAccents, runInZone, toInteger, toString };
1157
+ export { ContentRef, FOCUSABLE_ELEMENTS_SELECTOR, GetPipe, HUB_TRANSLATION_CONFIG, HubTranslationService, IsObjectPipe, IsObservablePipe, IsStringPipe, OverlayPosition, OverlayRef, OverlayService, PopupService, ScrollBar, TranslatePipe, UcfirstPipe, UnwrapAsyncPipe, closest, equals, getActiveElement, getFocusableBoundaryElements, getValue, getValueInRange, hubCompleteTransition, hubFocusTrap, hubRunTransition, interpolateString, isDefined, isInteger, isNumber, isPromise, isString, padNumber, reflow, regExpEscape, removeAccents, runInZone, toInteger, toString };
387
1158
  //# sourceMappingURL=ng-hub-ui-utils.mjs.map