@energycap/components 0.39.16 → 0.39.17-ECAP-23124-menu-item-divider-improvements.20240523-1609

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.
@@ -1479,205 +1479,229 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImpor
1479
1479
  }] } });
1480
1480
 
1481
1481
  /**
1482
- * Primitive directive that popups a container using PopperJS
1483
- *
1484
- * @export
1482
+ * Service to help with interfacing with the window object
1483
+ * and navigating around the application (going outside of the Angular 2+ router)
1485
1484
  */
1486
- class PopupContainerDirective {
1485
+ class WindowService {
1487
1486
  /**
1488
- * Creates an instance of PopupContainerDirective.
1489
- * @param templateRef Reference to the popup template
1490
- * @param viewContainer Reference to the view container
1491
- * @param document Reference to Document
1492
- * @memberof PopupContainerDirective
1487
+ * Tracks if there are any unsaved changes that the user could lose.
1488
+ *
1489
+ * It is set up as `get` only because it is set with `addNavigateAwayPrompt`.
1490
+ *
1491
+ * This also includes adding a prompt to the window itself (in addition to
1492
+ * working with the `CanDeactivateUnsavedChanges` guard) to cover page reloads
1493
+ * which do not trigger router events.
1493
1494
  */
1494
- constructor(templateRef, viewContainer, document, renderer) {
1495
- this.templateRef = templateRef;
1496
- this.viewContainer = viewContainer;
1497
- this.document = document;
1498
- this.renderer = renderer;
1495
+ get hasUnsavedChanges() {
1496
+ return this._hasUnsavedChanges;
1497
+ }
1498
+ /**
1499
+ * Expose the innerWidth on the window global. Protects against errors when code
1500
+ * is running on a non-browser platform.
1501
+ */
1502
+ get innerWidth() {
1503
+ return window ? window.innerWidth : undefined;
1504
+ }
1505
+ constructor(router, activatedRoute) {
1506
+ this.router = router;
1507
+ this.activatedRoute = activatedRoute;
1508
+ this._hasUnsavedChanges = false;
1499
1509
  /**
1500
- * Emit the {@link PopupStatus} when it changes
1510
+ * Function called when the window `beforeunload` event is fired.
1511
+ *
1512
+ * A reference to the function that was passed to `window.addEventListener`
1513
+ * must be retained for `window.removeEventListener` to function properly.
1514
+ *
1515
+ * Some browsers require the event's `returnValue` to be set to show the confirmation
1516
+ * dialog.
1517
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
1501
1518
  */
1502
- this.popperStatusChange = new EventEmitter();
1519
+ this.beforeUnloadFunction = (event) => {
1520
+ // Cancel the event as stated by the standard.
1521
+ event.preventDefault();
1522
+ // Chrome requires returnValue to be set.
1523
+ event.returnValue = '';
1524
+ };
1525
+ if (window) {
1526
+ this.resized = fromEvent(window, 'resize');
1527
+ }
1503
1528
  }
1504
1529
  /**
1505
- * Angular onInit lifecycle hook
1506
- * @see https://angular.io/guide/lifecycle-hooks
1530
+ * Navigates to the previous page the user had visited
1507
1531
  */
1508
- ngOnInit() {
1509
- this.templateViewRef = this.viewContainer.createEmbeddedView(this.templateRef);
1532
+ goBack() {
1533
+ window.history.back();
1510
1534
  }
1511
- /**
1512
- * Angular onDestroy lifecycle hook. Close and delete references. Unsubscribe observables
1513
- * @see https://angular.io/guide/lifecycle-hooks
1535
+ /**An abstraction around the browsers window history length.
1536
+ * Returns zero if unable to access or running outside a browser context
1514
1537
  */
1515
- ngOnDestroy() {
1516
- this.hide();
1538
+ getHistoryLength() {
1539
+ return window?.history?.length || 0;
1517
1540
  }
1518
1541
  /**
1519
- * Displays the templateRef as a popup
1542
+ * Navigate to any url you know the path to
1543
+ * @param url The URL to navigate to
1520
1544
  *
1521
- * @memberof PopupContainerDirective
1545
+ * @deprecated For legacy support only; use `router.navigateByUrl` instead
1522
1546
  */
1523
- show() {
1524
- if (PopupContainerDirective.GlobalPopupRef) {
1525
- if (PopupContainerDirective.GlobalPopupRef != this) {
1526
- PopupContainerDirective.GlobalPopupRef.hide();
1527
- PopupContainerDirective.GlobalPopupRef = undefined;
1547
+ async navigateToUrl(url) {
1548
+ try {
1549
+ if (url.indexOf('/app/') === 0) {
1550
+ await this.router.navigateByUrl(url.substring(5));
1551
+ }
1552
+ else {
1553
+ await this.router.navigateByUrl(url);
1528
1554
  }
1529
1555
  }
1530
- if (!this.globalCloseSubscription) {
1531
- this.globalCloseSubscription = fromEvent(this.document.body, "click").subscribe((event) => {
1532
- this.hide();
1533
- });
1534
- }
1535
- if (!this.popperRef) {
1536
- // Add the popper template as an embedded view since PopperJS
1537
- // manipulates DOM elements.
1538
- this.popupViewRef = this.viewContainer.createEmbeddedView(this.popup);
1539
- // Since popper needs real DOM elements, grab the first non-comment
1540
- // DOM element to use as our anchor.
1541
- let anchorElement = this.popupViewRef.rootNodes.find(elem => { return elem.nodeName !== "#text"; });
1542
- // Use the parents elements as our DOM elements to Popper
1543
- this.popperRef = new Popper(this.templateViewRef.rootNodes[0], anchorElement, this.popperOptions);
1544
- PopupContainerDirective.GlobalPopupRef = this;
1545
- this.popperStatusChange.emit('visible');
1556
+ catch (e) {
1557
+ // If the router throws we will try to navigate to the fully qualified url as a last ditch effort.
1558
+ // This can happen if we missed a link that needs to be converted to ng5 or our ng1Href directive
1559
+ // didn't handle a link correctly
1560
+ window.location.href = url;
1546
1561
  }
1547
1562
  }
1548
1563
  /**
1549
- * Hides the templateRef
1550
- *
1551
- * @memberof PopupContainerDirective
1564
+ * Adds a `beforeunload` function to the window to alert the user that there are about to leave
1565
+ * the current page and ask if they'd like to leave or stay
1552
1566
  */
1553
- hide() {
1554
- if (this.globalCloseSubscription) {
1555
- this.globalCloseSubscription.unsubscribe();
1556
- this.globalCloseSubscription = undefined;
1557
- }
1558
- if (this.popperRef && this.popupViewRef) {
1559
- this.popupViewRef.destroy();
1560
- this.popperRef.destroy();
1561
- this.popperRef = undefined;
1562
- this.popperStatusChange.emit('hidden');
1563
- }
1567
+ addNavigateAwayPrompt() {
1568
+ this._hasUnsavedChanges = true;
1569
+ window.addEventListener("beforeunload", this.beforeUnloadFunction);
1564
1570
  }
1565
1571
  /**
1566
- * Updates the popup container position
1572
+ * Removes the `beforeunload` function added to the window
1567
1573
  */
1568
- update() {
1569
- if (this.popperRef) {
1570
- this.popperRef.update();
1571
- }
1572
- }
1573
- fixPosition(minWidthNone, appendToBody = false) {
1574
- if (this.popperRef && this.popperRef['reference'] && this.popperRef['popper']) {
1575
- let popupEl = this.popperRef['popper'];
1576
- // Reset width style previously assigned because the content may have
1577
- // changed and the auto width would be different
1578
- this.renderer.removeStyle(popupEl, 'width');
1579
- this.renderer.setStyle(popupEl, 'position', 'fixed');
1580
- if (appendToBody) {
1581
- const bodyEl = this.document.querySelector('body');
1582
- const popupParent = this.renderer.parentNode(popupEl);
1583
- if (popupParent !== bodyEl) {
1584
- this.renderer.appendChild(bodyEl, popupEl);
1585
- }
1586
- }
1587
- let toggleEl = this.popperRef['reference'];
1588
- let width = popupEl.offsetWidth;
1589
- let boundaries = popupEl.getBoundingClientRect();
1590
- let left = boundaries.left;
1591
- let coords = toggleEl.getBoundingClientRect();
1592
- // Set the top of our menu to the bottom of the toggle element
1593
- let top = coords.bottom;
1594
- if (this.popperOptions && this.popperOptions.placement) {
1595
- if (this.popperOptions.placement === 'bottom-start' || this.popperOptions.placement === 'top-start') {
1596
- left = coords.left;
1597
- }
1598
- else {
1599
- left = coords.right - ((minWidthNone || width > coords.width) ? width : coords.width);
1600
- }
1601
- }
1602
- // if it won't fit (with 10px space before hitting the window edge), flip it
1603
- if (boundaries.height + top + 10 > window.innerHeight) {
1604
- top = coords.top - boundaries.height;
1605
- }
1606
- this.renderer.setStyle(popupEl, 'transform', 'none');
1607
- this.renderer.setStyle(popupEl, 'left', left + 'px');
1608
- this.renderer.setStyle(popupEl, 'top', top + 'px');
1609
- this.renderer.setStyle(popupEl, 'width', width + 'px');
1610
- if (!minWidthNone) {
1611
- this.renderer.setStyle(popupEl, 'min-width', coords.width + 'px');
1612
- }
1613
- }
1574
+ removeNavigateAwayPrompt() {
1575
+ this._hasUnsavedChanges = false;
1576
+ window.removeEventListener("beforeunload", this.beforeUnloadFunction);
1614
1577
  }
1615
- }
1616
- /**
1617
- * Global reference to the currently displayed popup; only
1618
- * one popup directive can be in `show` state at a given time.
1619
- *
1620
- * @memberof PopupContainerDirective
1621
- */
1622
- PopupContainerDirective.GlobalPopupRef = undefined;
1623
- PopupContainerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: PopupContainerDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: DOCUMENT }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
1624
- PopupContainerDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.9", type: PopupContainerDirective, selector: "[ecPopup]", inputs: { popup: ["ecPopup", "popup"], popperOptions: ["options", "popperOptions"] }, ngImport: i0 });
1625
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: PopupContainerDirective, decorators: [{
1626
- type: Directive,
1627
- args: [{ selector: '[ecPopup]' }]
1628
- }], ctorParameters: function () { return [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: undefined, decorators: [{
1629
- type: Inject,
1630
- args: [DOCUMENT]
1631
- }] }, { type: i0.Renderer2 }]; }, propDecorators: { popup: [{
1632
- type: Input,
1633
- args: ['ecPopup']
1634
- }], popperOptions: [{
1635
- type: Input,
1636
- args: ['options']
1637
- }] } });
1638
-
1639
- class ScrollService {
1640
- constructor() { }
1641
1578
  /**
1642
- * Given a container and the target element to scroll to, we will set the scroll top on
1643
- * the container to bring the target into view.
1579
+ * Send data to another window.
1644
1580
  *
1645
- * @param scrollContainerSelector A valid CSS selector string for the scroll container.
1646
- * @param targetElementSelector A valid CSS selector string for the target element.
1647
- * @param topPadding The amount of space to leave above the target
1648
- * to keep it from being pinned to the top of the scrollContainer. Defaults
1649
- * to 32px, the default height of a menu item.
1581
+ * __SECURITY RISK__ - Always use a specific target origin. Failing to provide a specific target origin can allow
1582
+ * malicious sites to receive the message.
1583
+ *
1584
+ * @param targetWindow - Window to send the message to
1585
+ * @param message - Data to send
1586
+ * @param targetOrigin - What the URI of the target window must be for the message to be sent.
1587
+ * If sending data to another EnergyCAP window, this should always be `window.location.origin` to ensure
1588
+ * that only instances of EnergyCAP app receive the message.
1650
1589
  */
1651
- scrollToItem(scrollContainerSelector, targetElementSelector, topPadding = 32) {
1652
- let scrollContainer = document.querySelector(scrollContainerSelector);
1653
- if (!scrollContainer) {
1654
- return;
1655
- }
1656
- let targetElement = scrollContainer.querySelector(targetElementSelector);
1657
- if (!targetElement) {
1658
- return;
1659
- }
1660
- let targetRect = targetElement.getBoundingClientRect();
1661
- let containerRect = scrollContainer.getBoundingClientRect();
1662
- // Only scroll if the target is outside of the view bounds of the container
1663
- if (targetRect.bottom > containerRect.bottom || targetRect.top < containerRect.top) {
1664
- scrollContainer.scrollTop =
1665
- (targetRect.top - containerRect.top) + scrollContainer.scrollTop - topPadding;
1666
- }
1590
+ postMessage(targetWindow, message, targetOrigin) {
1591
+ targetWindow.postMessage(message, targetOrigin);
1667
1592
  }
1668
1593
  /**
1669
- * Return the value of the scrollTop property for an HTMLElement that matches the selector
1670
- * @param scrollContainerSelector A valid CSS selector
1594
+ * Open a new window
1595
+ * @param url - The URL of the resource to be loaded
1671
1596
  */
1672
- getCurrentScrollPosition(scrollContainerSelector) {
1673
- let scrollContainer = document.querySelector(scrollContainerSelector);
1674
- if (scrollContainer) {
1675
- return scrollContainer.scrollTop;
1676
- }
1677
- else {
1678
- console.error(`Scroll container '${scrollContainerSelector}' does not exist.`);
1679
- return 0;
1680
- }
1597
+ openNew(url) {
1598
+ window.open(url, '_blank');
1599
+ }
1600
+ /**
1601
+ * A wrapper around the router for changing the query params for the current url
1602
+ * without creating a new history entry or removing any existing query parameters.
1603
+ * The provided params are updated if they already exist or added to the url if they don't
1604
+ *
1605
+ * @returns a promise that resolves to true if the navigation succeeds, false if something (like a guard) blocks it.
1606
+ * In normal use, the navigation should succeed unless we use query params to block access to a route the user is already on
1607
+ */
1608
+ async modifyHistoryQueryParamsSubset(queryParams) {
1609
+ return this.router.navigate([], {
1610
+ relativeTo: this.activatedRoute,
1611
+ replaceUrl: true,
1612
+ queryParams: queryParams,
1613
+ queryParamsHandling: 'merge',
1614
+ });
1615
+ }
1616
+ /**A wrapper around the default javascript confirm dialog to allow us to unit test dependent code.
1617
+ * Of course eventually we'd like to have pretty confirmations for everything, but in some cases it wasn't worth the extra time
1618
+ * so we're using this instead.
1619
+ */
1620
+ confirm(prompt) {
1621
+ return Promise.resolve(confirm(prompt));
1622
+ }
1623
+ /**
1624
+ * Close the current window or a window instance if one is provided
1625
+ * @param windowInstance - Window to close (optional)
1626
+ */
1627
+ closeWindow(windowInstance) {
1628
+ if (windowInstance) {
1629
+ windowInstance.close();
1630
+ }
1631
+ else {
1632
+ window.close();
1633
+ }
1634
+ }
1635
+ getLocation() {
1636
+ return window.location.pathname + window.location.hash;
1637
+ }
1638
+ /** Get the current value of the full url, including protocol, host and path */
1639
+ getFullUrl() {
1640
+ return window.location.href;
1641
+ }
1642
+ /** Get the current value of the base url, including protocol, domain and port (if explicitly specified) */
1643
+ getBaseUrl() {
1644
+ return window.location.origin;
1645
+ }
1646
+ /**
1647
+ * Reloads the browser window.
1648
+ * NOT RECOMMENDED. Seek other options for reloading content within Angular before resorting to this.
1649
+ */
1650
+ reloadWindow() {
1651
+ window.location.reload();
1652
+ }
1653
+ }
1654
+ WindowService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: WindowService, deps: [{ token: i1$2.Router }, { token: i1$2.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Injectable });
1655
+ WindowService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: WindowService, providedIn: 'root' });
1656
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: WindowService, decorators: [{
1657
+ type: Injectable,
1658
+ args: [{
1659
+ providedIn: 'root'
1660
+ }]
1661
+ }], ctorParameters: function () { return [{ type: i1$2.Router }, { type: i1$2.ActivatedRoute }]; } });
1662
+
1663
+ class ScrollService {
1664
+ constructor() { }
1665
+ /**
1666
+ * Given a container and the target element to scroll to, we will set the scroll top on
1667
+ * the container to bring the target into view.
1668
+ *
1669
+ * @param scrollContainerSelector A valid CSS selector string for the scroll container.
1670
+ * @param targetElementSelector A valid CSS selector string for the target element.
1671
+ * @param topPadding The amount of space to leave above the target
1672
+ * to keep it from being pinned to the top of the scrollContainer. Defaults
1673
+ * to 32px, the default height of a menu item.
1674
+ */
1675
+ scrollToItem(scrollContainerSelector, targetElementSelector, topPadding = 32) {
1676
+ let scrollContainer = document.querySelector(scrollContainerSelector);
1677
+ if (!scrollContainer) {
1678
+ return;
1679
+ }
1680
+ let targetElement = scrollContainer.querySelector(targetElementSelector);
1681
+ if (!targetElement) {
1682
+ return;
1683
+ }
1684
+ let targetRect = targetElement.getBoundingClientRect();
1685
+ let containerRect = scrollContainer.getBoundingClientRect();
1686
+ // Only scroll if the target is outside of the view bounds of the container
1687
+ if (targetRect.bottom > containerRect.bottom || targetRect.top < containerRect.top) {
1688
+ scrollContainer.scrollTop =
1689
+ (targetRect.top - containerRect.top) + scrollContainer.scrollTop - topPadding;
1690
+ }
1691
+ }
1692
+ /**
1693
+ * Return the value of the scrollTop property for an HTMLElement that matches the selector
1694
+ * @param scrollContainerSelector A valid CSS selector
1695
+ */
1696
+ getCurrentScrollPosition(scrollContainerSelector) {
1697
+ let scrollContainer = document.querySelector(scrollContainerSelector);
1698
+ if (scrollContainer) {
1699
+ return scrollContainer.scrollTop;
1700
+ }
1701
+ else {
1702
+ console.error(`Scroll container '${scrollContainerSelector}' does not exist.`);
1703
+ return 0;
1704
+ }
1681
1705
  }
1682
1706
  /**
1683
1707
  * Set the scrollTop of an HTMLElement that matches the selector to a specific position
@@ -1723,922 +1747,926 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImpor
1723
1747
  }]
1724
1748
  }], ctorParameters: function () { return []; } });
1725
1749
 
1726
- /** Advanced validation for textbox form controls */
1727
- const textboxValidation = (validatorParams) => {
1728
- return (control) => {
1729
- let validators = [];
1730
- // Innocent until proven guilty
1731
- validatorParams.valid = true;
1732
- if (validatorParams.required) {
1733
- validators.push(Validators.required);
1734
- }
1735
- if (validatorParams.minLength !== undefined) {
1736
- validators.push(Validators.minLength(validatorParams.minLength));
1737
- }
1738
- if (validatorParams.maxLength !== undefined) {
1739
- validators.push(Validators.maxLength(validatorParams.maxLength));
1740
- }
1741
- if (validatorParams.pattern !== undefined) {
1742
- validators.push(Validators.pattern(validatorParams.pattern));
1743
- }
1744
- validators.forEach(validator => {
1745
- let validationResult = validator(control);
1746
- if (validationResult) {
1747
- validatorParams.valid = false;
1748
- }
1749
- });
1750
- if (validatorParams.valid) {
1751
- return null;
1752
- }
1753
- else {
1754
- return {
1755
- textbox: validatorParams
1756
- };
1757
- }
1758
- };
1759
- };
1760
- const phoneNumberValidationPattern = '^\\s*(?:\\+?(\\d{1,3}))?[-. (]*(\\d{3})[-. )]*(\\d{3})[-. ]*(\\d{4})(?: *x(\\d+))?\\s*$';
1761
- const urlValidationPattern = '([A-Za-z])+(:\/\/)+[^\\s]*';
1762
- class TextboxComponent extends FormControlBase {
1763
- constructor(validationMessageService, formGroupHelper, translate) {
1764
- super(validationMessageService, formGroupHelper);
1765
- this.validationMessageService = validationMessageService;
1766
- this.formGroupHelper = formGroupHelper;
1767
- this.translate = translate;
1768
- /**
1769
- * Set the value of the input's autocomplete attribute
1770
- */
1771
- this.autocomplete = 'off';
1772
- /**
1773
- * The textbox type
1774
- */
1775
- this.type = "text";
1776
- /**
1777
- * The value of the rows attribute for a textarea. Only applies to multi-line type
1778
- */
1779
- this.rows = 3;
1780
- /**
1781
- * If set to true, we will select all text within the input if
1782
- * autofocus is also set to true
1783
- */
1784
- this.selectOnAutofocus = false;
1785
- /**
1786
- * If set to true, we will upper case on focus out
1787
- */
1788
- this.upperCase = false;
1789
- /**
1790
- * Validation pattern for the input. This is determined on the input type specified
1791
- */
1792
- this.validationPattern = '';
1793
- }
1794
- ngOnChanges(changes) {
1795
- super.ngOnChanges(changes);
1796
- }
1750
+ class NavItemActiveDirective {
1797
1751
  /**
1798
- * The angular onInit lifecycle hook
1752
+ * Determines whether the directive will try to make an exact match on the url or not
1753
+ * If false, the directive will add the active class if the first part of the url matches
1754
+ * the active route.
1755
+ * see: https://angular.io/api/router/Router#isactive
1799
1756
  */
1800
- ngOnInit() {
1801
- super.ngOnInit();
1802
- this.validationPattern = '';
1803
- if (this.type === 'tel') {
1804
- this.validationPattern = phoneNumberValidationPattern;
1805
- }
1806
- else if (this.type === 'url') {
1807
- this.validationPattern = urlValidationPattern;
1757
+ set exact(value) {
1758
+ if (value === undefined) {
1759
+ this._exact = true;
1808
1760
  }
1809
- if (this.placeholder) {
1810
- this.translate.get(this.placeholder)
1811
- .subscribe((translated) => {
1812
- this.placeholder = translated;
1813
- });
1761
+ else {
1762
+ this._exact = value;
1814
1763
  }
1764
+ this.update();
1815
1765
  }
1816
1766
  /**
1817
- * The angular afterViewInit lifecycle hook
1767
+ * The url of the NavItem to check for activeness. Convert the item url into a
1768
+ * UrlTree relative to the ActivatedRoute so router#isActive works even with relative urls.
1769
+ * See Angular's [routerLink](https://github.com/angular/angular/blob/8282e15c2becbe42a49befa07d6407247e8243d8/packages/router/src/directives/router_link.ts#L249)
1770
+ * and [routerLinkActive](https://github.com/angular/angular/blob/8282e15c2becbe42a49befa07d6407247e8243d8/packages/router/src/directives/router_link_active.ts#L139)
1771
+ * for a similiar implementation.
1818
1772
  */
1819
- ngAfterViewInit() {
1820
- if (this.autofocus) {
1821
- this.setFocus(this.selectOnAutofocus);
1773
+ set url(value) {
1774
+ if (value !== null && value !== undefined) {
1775
+ this._url = this.router.createUrlTree([value], { relativeTo: this.route, queryParams: this.queryParams });
1776
+ this.update();
1822
1777
  }
1823
1778
  }
1824
- /**
1825
- * Function to set focus on an input programmatically after the page
1826
- * had been rendered. The highlight text flag will select the text
1827
- * within the input if passed in and true
1828
- */
1829
- setFocus(highlightText) {
1830
- this.inputElement.nativeElement.focus();
1831
- if (highlightText) {
1832
- this.inputElement.nativeElement.select();
1833
- }
1779
+ constructor(router, el, renderer, route) {
1780
+ this.router = router;
1781
+ this.el = el;
1782
+ this.renderer = renderer;
1783
+ this.route = route;
1784
+ this._exact = true;
1785
+ /** Emits when the url becomes active */
1786
+ this.routerLinkActivated = new EventEmitter();
1787
+ /** Subject that emits when component is destroyed to unsubscribe from any subscriptions */
1788
+ this.destroyed = new Subject();
1834
1789
  }
1835
- /**
1836
- * Focus out event handler
1837
- * will upper case and trim value if upperCase is true (this is what we do on the apis)
1838
- */
1839
- focusOutEvent() {
1840
- if (this.upperCase && this.formModel.value) {
1841
- this.formModel.setValue(this.formModel.value.toUpperCase().trim());
1842
- }
1790
+ /** Check if url is active on NavigationEnd events */
1791
+ ngOnInit() {
1792
+ this.router.events.pipe(takeUntil(this.destroyed), filter(e => e instanceof NavigationEnd)).subscribe(() => {
1793
+ this.update();
1794
+ });
1843
1795
  }
1844
- }
1845
- TextboxComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: TextboxComponent, deps: [{ token: ValidationMessageService }, { token: FormGroupHelper }, { token: i2.TranslateService }], target: i0.ɵɵFactoryTarget.Component });
1846
- TextboxComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: TextboxComponent, selector: "ec-textbox", inputs: { autocomplete: "autocomplete", type: "type", placeholder: "placeholder", maxlength: "maxlength", minlength: "minlength", rows: "rows", selectOnAutofocus: "selectOnAutofocus", upperCase: "upperCase" }, viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["textboxInput"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"control control-label-{{labelPosition}}\"\r\n [ngClass]=\"{'is-readonly': readonly}\">\r\n\r\n <label *ngIf=\"label\">\r\n <span>{{label | translate}}</span>\r\n <span *ngIf=\"validationErrors.length > 0 && formModel.touched && formModel.invalid\">&nbsp;{{validationErrors |\r\n translate}}</span>\r\n <ec-help-popover id=\"{{id}}_helpPopover\"\r\n *ngIf=\"helpPopover\"\r\n class=\"d-inline-block my-n3 mx-n1\"\r\n text=\"{{helpPopover | translate}}\"\r\n contentPosition=\"{{helpPopoverPosition}}\">\r\n </ec-help-popover>\r\n </label>\r\n\r\n <div class=\"input-wrapper control-input\">\r\n <input *ngIf=\"type !== 'multi_line'\"\r\n #textboxInput\r\n email=\"{{type === 'email' ? true : false}}\"\r\n pattern=\"{{validationPattern}}\"\r\n type=\"{{type}}\"\r\n tabindex=\"{{tabindex}}\"\r\n title=\"{{tooltip}}\"\r\n [attr.id]=\"inputId\"\r\n [attr.autocomplete]=\"autocomplete\"\r\n [attr.placeholder]=\"placeholder\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [attr.required]=\"required ? required : null\"\r\n [formControl]=\"formModel\"\r\n [ngClass]=\"{'is-empty': !formModel?.value, 'is-pending': pending, 'is-uppercase': upperCase}\"\r\n (focusout)=\"focusOutEvent()\"\r\n [attr.cdkFocusInitial]=\"autofocus || null\">\r\n\r\n <textarea *ngIf=\"type === 'multi_line'\"\r\n [attr.rows]=\"rows\"\r\n #textboxInput\r\n tabindex=\"{{tabindex}}\"\r\n [attr.id]=\"inputId\"\r\n [attr.placeholder]=\"placeholder\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [attr.required]=\"required ? required : null\"\r\n [formControl]=\"formModel\"\r\n [ngClass]=\"{'is-empty': formModel?.value === '', 'is-pending': pending}\"\r\n [attr.cdkFocusInitial]=\"autofocus || null\">\r\n </textarea>\r\n\r\n <i class=\"ec-icon icon-required\"></i>\r\n <i class=\"ec-icon icon-invalid\"></i>\r\n <i class=\"ec-icon icon-loading\"></i>\r\n </div>\r\n</div>", styles: [":host{color:var(--ec-form-control-color);font-size:var(--ec-form-control-font-size);display:block;margin-bottom:1rem;width:100%}:host :host-context(.form-condensed){margin-bottom:.5rem}:host .control{width:100%;display:flex;flex-direction:column}:host .control.control-label-bottom{flex-direction:column-reverse}:host .control.control-label-left{flex-direction:row}:host .control.control-label-left label{margin-right:.25rem}:host .control.control-label-right{flex-direction:row-reverse}:host .control.control-label-right label{margin-left:.25rem}:host .control.control-label-left,:host .control.control-label-right{align-items:center}:host .control.control-label-left label,:host .control.control-label-right label{flex:1 1;margin-top:0;margin-bottom:0}:host .control.control-label-left .control-input,:host .control.control-label-right .control-input{flex:2 2}:host .control.is-readonly input,:host .control.is-readonly select,:host .control.is-readonly textarea{border-color:var(--ec-form-control-border-color-readonly);background-color:var(--ec-form-control-background-color-readonly);background-clip:border-box;background-image:none;color:var(--ec-form-control-color-readonly);opacity:1;-webkit-user-select:none;user-select:none;pointer-events:none;overflow:hidden;white-space:nowrap}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid:focus,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid:focus{border-color:var(--ec-form-control-background-color-invalid)}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid~.icon-invalid,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid~.icon-required,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid~.icon-required{display:none}:host .control.invalid:not(.open) .textbox-group-btn-right ::ng-deep button{background-color:var(--ec-form-control-background-color-invalid)}:host .control.invalid:not(.open) .textbox-group-btn-right ::ng-deep button:not(:focus){border-color:var(--ec-form-control-border-color-invalid)}:host .textbox-group{display:flex;position:relative}:host textarea:focus,:host input:focus,:host select:focus{outline:none}:host label{color:var(--ec-form-control-label-color, var(--ec-color-secondary-dark));display:block;font-size:var(--ec-font-size-label);line-height:1;margin:calc(var(--ec-font-size-label) / 2) 0}:host input{background-color:var(--ec-form-control-background-color);border:1px solid var(--ec-form-control-border-color);border-radius:var(--ec-border-radius);background-image:none;background-clip:padding-box;width:100%;line-height:1.25rem;padding:.3125rem .5rem;height:2rem}:host input::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host input::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host input::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host input:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host input:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host input~.icon-required,:host input~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host input:required.is-empty{background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host input:required.is-empty~.icon-required{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host input.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host input.ng-invalid.ng-touched~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.ng-invalid.ng-touched~.icon-required{display:none}:host input.is-pending.ng-valid,:host input.is-pending.ng-invalid,:host input.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host input.is-pending.ng-valid~.icon-loading,:host input.is-pending.ng-invalid~.icon-loading,:host input.is-pending.ng-pending~.icon-loading{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.is-pending.ng-valid~.icon-required,:host input.is-pending.ng-valid~.icon-invalid,:host input.is-pending.ng-invalid~.icon-required,:host input.is-pending.ng-invalid~.icon-invalid,:host input.is-pending.ng-pending~.icon-required,:host input.is-pending.ng-pending~.icon-invalid{display:none}:host input:focus,:host input:focus.is-empty{border-color:var(--ec-form-control-border-color-focus);box-shadow:var(--ec-form-control-box-shadow-focus);position:relative;z-index:1}:host input:disabled{background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled);color:var(--ec-form-control-color-disabled);opacity:var(--ec-form-control-opacity-disabled)}:host input:disabled:required,:host input:disabled:required.is-empty{background-image:none;padding-left:.5rem;background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled)}:host input:disabled:required+.icon-required,:host input:disabled:required.is-empty+.icon-required{display:none}:host input.is-uppercase:not(.is-empty){text-transform:uppercase}:host textarea{background-color:var(--ec-form-control-background-color);border:1px solid var(--ec-form-control-border-color);border-radius:var(--ec-border-radius);background-image:none;background-clip:padding-box;width:100%;line-height:1.25rem;padding:.3125rem .5rem;height:auto;resize:none;display:block}:host textarea::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host textarea::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host textarea::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host textarea:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host textarea:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host textarea~.icon-required,:host textarea~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host textarea:required.is-empty{background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host textarea:required.is-empty~.icon-required{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host textarea.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host textarea.ng-invalid.ng-touched~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.ng-invalid.ng-touched~.icon-required{display:none}:host textarea.is-pending.ng-valid,:host textarea.is-pending.ng-invalid,:host textarea.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem}:host textarea.is-pending.ng-valid~.icon-loading,:host textarea.is-pending.ng-invalid~.icon-loading,:host textarea.is-pending.ng-pending~.icon-loading{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.is-pending.ng-valid~.icon-required,:host textarea.is-pending.ng-valid~.icon-invalid,:host textarea.is-pending.ng-invalid~.icon-required,:host textarea.is-pending.ng-invalid~.icon-invalid,:host textarea.is-pending.ng-pending~.icon-required,:host textarea.is-pending.ng-pending~.icon-invalid{display:none}:host textarea:focus,:host textarea:focus.is-empty{border-color:var(--ec-form-control-border-color-focus);box-shadow:var(--ec-form-control-box-shadow-focus);position:relative;z-index:1}:host textarea:disabled{background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled);color:var(--ec-form-control-color-disabled);opacity:var(--ec-form-control-opacity-disabled)}:host textarea:disabled:required,:host textarea:disabled:required.is-empty{background-image:none;padding-left:.5rem;background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled)}:host textarea:disabled:required+.icon-required,:host textarea:disabled:required.is-empty+.icon-required{display:none}:host textarea.is-uppercase:not(.is-empty){text-transform:uppercase}.input-wrapper{position:relative}.input-wrapper>.ec-icon{display:none}:host(.textbox-group-input:not(:last-child)){flex:1 1 0%;width:1px}:host(.textbox-group-input:not(:last-child)) .control{margin-bottom:0}:host(.textbox-group-input:not(:last-child)) .control.is-readonly input{border-right-width:1px}:host(.textbox-group-input:not(:last-child)) input{border-top-right-radius:0;border-bottom-right-radius:0;border-right-width:0}:host(.textbox-group-input:not(:last-child)) input:focus{position:relative;z-index:1;border-right-width:1px}:host(.text-truncate) input{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host(.is-monospace) input,:host(.is-monospace) textarea,:host-context(.is-monospace) input,:host-context(.is-monospace) textarea{font-family:var(--ec-font-family-monospace)}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i4.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i4.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i4.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: HelpPopoverComponent, selector: "ec-help-popover", inputs: ["id", "text", "contentPosition", "maxWidth"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }] });
1847
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: TextboxComponent, decorators: [{
1848
- type: Component,
1849
- args: [{ selector: 'ec-textbox', template: "<div class=\"control control-label-{{labelPosition}}\"\r\n [ngClass]=\"{'is-readonly': readonly}\">\r\n\r\n <label *ngIf=\"label\">\r\n <span>{{label | translate}}</span>\r\n <span *ngIf=\"validationErrors.length > 0 && formModel.touched && formModel.invalid\">&nbsp;{{validationErrors |\r\n translate}}</span>\r\n <ec-help-popover id=\"{{id}}_helpPopover\"\r\n *ngIf=\"helpPopover\"\r\n class=\"d-inline-block my-n3 mx-n1\"\r\n text=\"{{helpPopover | translate}}\"\r\n contentPosition=\"{{helpPopoverPosition}}\">\r\n </ec-help-popover>\r\n </label>\r\n\r\n <div class=\"input-wrapper control-input\">\r\n <input *ngIf=\"type !== 'multi_line'\"\r\n #textboxInput\r\n email=\"{{type === 'email' ? true : false}}\"\r\n pattern=\"{{validationPattern}}\"\r\n type=\"{{type}}\"\r\n tabindex=\"{{tabindex}}\"\r\n title=\"{{tooltip}}\"\r\n [attr.id]=\"inputId\"\r\n [attr.autocomplete]=\"autocomplete\"\r\n [attr.placeholder]=\"placeholder\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [attr.required]=\"required ? required : null\"\r\n [formControl]=\"formModel\"\r\n [ngClass]=\"{'is-empty': !formModel?.value, 'is-pending': pending, 'is-uppercase': upperCase}\"\r\n (focusout)=\"focusOutEvent()\"\r\n [attr.cdkFocusInitial]=\"autofocus || null\">\r\n\r\n <textarea *ngIf=\"type === 'multi_line'\"\r\n [attr.rows]=\"rows\"\r\n #textboxInput\r\n tabindex=\"{{tabindex}}\"\r\n [attr.id]=\"inputId\"\r\n [attr.placeholder]=\"placeholder\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [attr.required]=\"required ? required : null\"\r\n [formControl]=\"formModel\"\r\n [ngClass]=\"{'is-empty': formModel?.value === '', 'is-pending': pending}\"\r\n [attr.cdkFocusInitial]=\"autofocus || null\">\r\n </textarea>\r\n\r\n <i class=\"ec-icon icon-required\"></i>\r\n <i class=\"ec-icon icon-invalid\"></i>\r\n <i class=\"ec-icon icon-loading\"></i>\r\n </div>\r\n</div>", styles: [":host{color:var(--ec-form-control-color);font-size:var(--ec-form-control-font-size);display:block;margin-bottom:1rem;width:100%}:host :host-context(.form-condensed){margin-bottom:.5rem}:host .control{width:100%;display:flex;flex-direction:column}:host .control.control-label-bottom{flex-direction:column-reverse}:host .control.control-label-left{flex-direction:row}:host .control.control-label-left label{margin-right:.25rem}:host .control.control-label-right{flex-direction:row-reverse}:host .control.control-label-right label{margin-left:.25rem}:host .control.control-label-left,:host .control.control-label-right{align-items:center}:host .control.control-label-left label,:host .control.control-label-right label{flex:1 1;margin-top:0;margin-bottom:0}:host .control.control-label-left .control-input,:host .control.control-label-right .control-input{flex:2 2}:host .control.is-readonly input,:host .control.is-readonly select,:host .control.is-readonly textarea{border-color:var(--ec-form-control-border-color-readonly);background-color:var(--ec-form-control-background-color-readonly);background-clip:border-box;background-image:none;color:var(--ec-form-control-color-readonly);opacity:1;-webkit-user-select:none;user-select:none;pointer-events:none;overflow:hidden;white-space:nowrap}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid:focus,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid:focus{border-color:var(--ec-form-control-background-color-invalid)}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid~.icon-invalid,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid~.icon-required,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid~.icon-required{display:none}:host .control.invalid:not(.open) .textbox-group-btn-right ::ng-deep button{background-color:var(--ec-form-control-background-color-invalid)}:host .control.invalid:not(.open) .textbox-group-btn-right ::ng-deep button:not(:focus){border-color:var(--ec-form-control-border-color-invalid)}:host .textbox-group{display:flex;position:relative}:host textarea:focus,:host input:focus,:host select:focus{outline:none}:host label{color:var(--ec-form-control-label-color, var(--ec-color-secondary-dark));display:block;font-size:var(--ec-font-size-label);line-height:1;margin:calc(var(--ec-font-size-label) / 2) 0}:host input{background-color:var(--ec-form-control-background-color);border:1px solid var(--ec-form-control-border-color);border-radius:var(--ec-border-radius);background-image:none;background-clip:padding-box;width:100%;line-height:1.25rem;padding:.3125rem .5rem;height:2rem}:host input::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host input::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host input::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host input:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host input:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host input~.icon-required,:host input~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host input:required.is-empty{background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host input:required.is-empty~.icon-required{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host input.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host input.ng-invalid.ng-touched~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.ng-invalid.ng-touched~.icon-required{display:none}:host input.is-pending.ng-valid,:host input.is-pending.ng-invalid,:host input.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host input.is-pending.ng-valid~.icon-loading,:host input.is-pending.ng-invalid~.icon-loading,:host input.is-pending.ng-pending~.icon-loading{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.is-pending.ng-valid~.icon-required,:host input.is-pending.ng-valid~.icon-invalid,:host input.is-pending.ng-invalid~.icon-required,:host input.is-pending.ng-invalid~.icon-invalid,:host input.is-pending.ng-pending~.icon-required,:host input.is-pending.ng-pending~.icon-invalid{display:none}:host input:focus,:host input:focus.is-empty{border-color:var(--ec-form-control-border-color-focus);box-shadow:var(--ec-form-control-box-shadow-focus);position:relative;z-index:1}:host input:disabled{background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled);color:var(--ec-form-control-color-disabled);opacity:var(--ec-form-control-opacity-disabled)}:host input:disabled:required,:host input:disabled:required.is-empty{background-image:none;padding-left:.5rem;background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled)}:host input:disabled:required+.icon-required,:host input:disabled:required.is-empty+.icon-required{display:none}:host input.is-uppercase:not(.is-empty){text-transform:uppercase}:host textarea{background-color:var(--ec-form-control-background-color);border:1px solid var(--ec-form-control-border-color);border-radius:var(--ec-border-radius);background-image:none;background-clip:padding-box;width:100%;line-height:1.25rem;padding:.3125rem .5rem;height:auto;resize:none;display:block}:host textarea::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host textarea::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host textarea::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host textarea:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host textarea:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host textarea~.icon-required,:host textarea~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host textarea:required.is-empty{background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host textarea:required.is-empty~.icon-required{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host textarea.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host textarea.ng-invalid.ng-touched~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.ng-invalid.ng-touched~.icon-required{display:none}:host textarea.is-pending.ng-valid,:host textarea.is-pending.ng-invalid,:host textarea.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem}:host textarea.is-pending.ng-valid~.icon-loading,:host textarea.is-pending.ng-invalid~.icon-loading,:host textarea.is-pending.ng-pending~.icon-loading{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.is-pending.ng-valid~.icon-required,:host textarea.is-pending.ng-valid~.icon-invalid,:host textarea.is-pending.ng-invalid~.icon-required,:host textarea.is-pending.ng-invalid~.icon-invalid,:host textarea.is-pending.ng-pending~.icon-required,:host textarea.is-pending.ng-pending~.icon-invalid{display:none}:host textarea:focus,:host textarea:focus.is-empty{border-color:var(--ec-form-control-border-color-focus);box-shadow:var(--ec-form-control-box-shadow-focus);position:relative;z-index:1}:host textarea:disabled{background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled);color:var(--ec-form-control-color-disabled);opacity:var(--ec-form-control-opacity-disabled)}:host textarea:disabled:required,:host textarea:disabled:required.is-empty{background-image:none;padding-left:.5rem;background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled)}:host textarea:disabled:required+.icon-required,:host textarea:disabled:required.is-empty+.icon-required{display:none}:host textarea.is-uppercase:not(.is-empty){text-transform:uppercase}.input-wrapper{position:relative}.input-wrapper>.ec-icon{display:none}:host(.textbox-group-input:not(:last-child)){flex:1 1 0%;width:1px}:host(.textbox-group-input:not(:last-child)) .control{margin-bottom:0}:host(.textbox-group-input:not(:last-child)) .control.is-readonly input{border-right-width:1px}:host(.textbox-group-input:not(:last-child)) input{border-top-right-radius:0;border-bottom-right-radius:0;border-right-width:0}:host(.textbox-group-input:not(:last-child)) input:focus{position:relative;z-index:1;border-right-width:1px}:host(.text-truncate) input{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host(.is-monospace) input,:host(.is-monospace) textarea,:host-context(.is-monospace) input,:host-context(.is-monospace) textarea{font-family:var(--ec-font-family-monospace)}\n"] }]
1850
- }], ctorParameters: function () { return [{ type: ValidationMessageService }, { type: FormGroupHelper }, { type: i2.TranslateService }]; }, propDecorators: { autocomplete: [{
1851
- type: Input
1852
- }], type: [{
1853
- type: Input
1854
- }], placeholder: [{
1855
- type: Input
1856
- }], maxlength: [{
1857
- type: Input
1858
- }], minlength: [{
1859
- type: Input
1860
- }], rows: [{
1861
- type: Input
1862
- }], selectOnAutofocus: [{
1863
- type: Input
1864
- }], upperCase: [{
1865
- type: Input
1866
- }], inputElement: [{
1867
- type: ViewChild,
1868
- args: ['textboxInput']
1869
- }] } });
1870
-
1871
- /** Exposes the markup and styles that represent the spinner. No inputs or outputs defined because it is just a visual component*/
1872
- class SpinnerComponent {
1873
- }
1874
- SpinnerComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: SpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1875
- SpinnerComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: SpinnerComponent, selector: "ec-spinner", ngImport: i0, template: "<div class=\"spinner\">\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n</div>", styles: ["@keyframes sk-bouncedelay{0%,80%,to{opacity:0}40%{opacity:1}}.spinner{display:flex}.spinner-dot{width:.75rem;height:.75rem;background-color:var(--ec-color-interactive);animation:sk-bouncedelay 1.7s infinite ease-in-out both;margin-right:.25rem}.spinner-dot:nth-child(1){animation-delay:-.6s}.spinner-dot:nth-child(2){animation-delay:-.4s}.spinner-dot:nth-child(3){animation-delay:-.2s}:host(.spinner-small) .spinner-dot{width:.5rem;height:.5rem;background-color:var(--ec-color-interactive);animation:sk-bouncedelay 1.7s infinite ease-in-out both;margin-right:.1666666667rem}:host(.spinner-small) .spinner-dot:nth-child(1){animation-delay:-.6s}:host(.spinner-small) .spinner-dot:nth-child(2){animation-delay:-.4s}:host(.spinner-small) .spinner-dot:nth-child(3){animation-delay:-.2s}\n"] });
1876
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: SpinnerComponent, decorators: [{
1877
- type: Component,
1878
- args: [{ selector: 'ec-spinner', template: "<div class=\"spinner\">\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n</div>", styles: ["@keyframes sk-bouncedelay{0%,80%,to{opacity:0}40%{opacity:1}}.spinner{display:flex}.spinner-dot{width:.75rem;height:.75rem;background-color:var(--ec-color-interactive);animation:sk-bouncedelay 1.7s infinite ease-in-out both;margin-right:.25rem}.spinner-dot:nth-child(1){animation-delay:-.6s}.spinner-dot:nth-child(2){animation-delay:-.4s}.spinner-dot:nth-child(3){animation-delay:-.2s}:host(.spinner-small) .spinner-dot{width:.5rem;height:.5rem;background-color:var(--ec-color-interactive);animation:sk-bouncedelay 1.7s infinite ease-in-out both;margin-right:.1666666667rem}:host(.spinner-small) .spinner-dot:nth-child(1){animation-delay:-.6s}:host(.spinner-small) .spinner-dot:nth-child(2){animation-delay:-.4s}:host(.spinner-small) .spinner-dot:nth-child(3){animation-delay:-.2s}\n"] }]
1879
- }] });
1880
-
1881
- class Overlay {
1882
- constructor(status, message) {
1883
- this.status = 'hasData';
1884
- this.message = '';
1885
- this.setStatus(status, message);
1886
- }
1887
- setStatus(status, message, action, noDataTemplate, overlayClassList) {
1888
- this.status = status;
1889
- this.message = message || '';
1890
- this.action = action || undefined;
1891
- this.noDataTemplate = noDataTemplate || undefined;
1892
- this.overlayClassList = overlayClassList || '';
1893
- }
1894
- }
1895
- /**
1896
- * Wraps content in order to show pending, error, and no data states with an optional message/noDataTemplate
1897
- */
1898
- class ViewOverlayComponent {
1899
- constructor() {
1900
- this.status = 'hasData';
1901
- }
1902
- setStatus(status, message, action, noDataTemplate) {
1903
- this.status = status;
1904
- this.message = message || '';
1905
- this.action = action || undefined;
1906
- this.noDataTemplate = noDataTemplate || undefined;
1796
+ ngOnDestroy() {
1797
+ this.destroyed.next();
1798
+ this.destroyed.unsubscribe();
1907
1799
  }
1908
- actionClicked(event) {
1909
- if (this.action && this.action.onClick) {
1910
- this.action.onClick(event);
1800
+ /** If url is active apply the defined class to the element, otherwise remove it */
1801
+ update() {
1802
+ if (this._url && this.classValue) {
1803
+ if (this.router.isActive(this._url, { matrixParams: 'ignored', queryParams: this._exact ? 'exact' : 'subset', paths: this._exact ? 'exact' : 'subset', fragment: 'ignored' })) {
1804
+ this.renderer.addClass(this.el.nativeElement, this.classValue);
1805
+ this.routerLinkActivated.emit(new Event('routerLinkActivated'));
1806
+ }
1807
+ else {
1808
+ this.renderer.removeClass(this.el.nativeElement, this.classValue);
1809
+ }
1911
1810
  }
1912
1811
  }
1913
1812
  }
1914
- ViewOverlayComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: ViewOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1915
- ViewOverlayComponentcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: ViewOverlayComponent, selector: "[ecOverlay]", inputs: { status: "status", message: "message", action: "action", noDataTemplate: "noDataTemplate", displayAsMask: "displayAsMask", overlayClassList: "overlayClassList" }, ngImport: i0, template: "<!-- Transcluded Content -->\r\n<ng-content *ngIf=\"displayAsMask || (!displayAsMask && status === 'hasData')\"></ng-content>\r\n<!--Used by GI tests to know the overlay status whether we use ngIf or mask version. No visual impact-->\r\n<span [hidden]=\"true\"\r\n\t class=\"overlay-status-{{status}}\"></span>\r\n<!-- Overlay goes last so it is rendered on top of preceding content due to source order -->\r\n<div *ngIf=\"status !== 'hasData'\"\r\n\t class=\"overlay flex-grow {{overlayClassList}}\"\r\n\t [ngClass]=\"{'not-mask': !displayAsMask,\r\n\t\t\t\t'overlay-error': status === 'error',\r\n\t\t\t\t'overlay-nodata': status === 'noData',\r\n\t\t\t\t'overlay-pending': status === 'pending'}\">\r\n\r\n\t<!--Pending Spinner-->\r\n\t<ec-spinner [hidden]=\"status !== 'pending'\"></ec-spinner>\r\n\r\n\t<ng-template [ngIf]=\"status === 'noData' && noDataTemplate\">\r\n\t\t<ng-container *ngTemplateOutlet=\"noDataTemplate\"></ng-container>\r\n\t</ng-template>\r\n\r\n\t<ng-container *ngIf=\"(status === 'noData' && !noDataTemplate) || status !== 'noData'\">\r\n\t\t<!--Status Message-->\r\n\t\t<div id=\"statusMessage\"\r\n\t\t\t class=\"message\"\r\n\t\t\t *ngIf=\"message\"\r\n\t\t\t [ngClass]=\"{'error': status === 'error', 'mt-1': status === 'pending'}\"\r\n\t\t\t [innerHtml]=\"message | translate\">\r\n\t\t</div>\r\n\r\n\t\t<!-- Action -->\r\n\t\t<ec-button type=\"common\"\r\n\t\t\t\t class=\"mt-3\"\r\n\t\t\t\t *ngIf=\"action?.onClick\"\r\n\t\t\t\t [icon]=\"action?.icon\"\r\n\t\t\t\t (clicked)=\"actionClicked($event)\"\r\n\t\t\t\t [label]=\"action?.label\"\r\n\t\t\t\t [hidden]=\"status === 'pending'\">\r\n\t\t</ec-button>\r\n\t</ng-container>\r\n\r\n</div>", styles: [":host{position:relative}:host(.bg-body)>.overlay{background-color:var(--ec-background-color-body)}:host(.bg-body).is-translucent>.overlay{background-color:var(--ec-background-color-overlay)}:host(.bg-content)>.overlay{background-color:var(--ec-background-color)}:host(.bg-content).is-translucent>.overlay{background-color:var(--ec-background-color-overlay)}.overlay{align-items:center;background-color:var(--ec-overlay-background-color, var(--ec-background-color));display:flex;flex-direction:column;justify-content:center;padding:3rem 4rem;z-index:var(--ec-z-index-overlay);position:absolute;inset:0}.overlay.not-mask{position:relative;min-height:100%}.message{color:var(--ec-color-secondary-dark);font-size:var(--ec-font-size-title)}.message.error{color:var(--ec-color-danger);font-size:var(--ec-font-size-title)}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ButtonComponent, selector: "ec-button", inputs: ["id", "disabled", "icon", "label", "badge", "tabindex", "type", "pending", "pendingIcon", "customTemplate", "isSubmit", "autofocus"], outputs: ["clicked"] }, { kind: "component", type: SpinnerComponent, selector: "ec-spinner" }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }] });
1916
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: ViewOverlayComponent, decorators: [{
1917
- type: Component,
1918
- args: [{ selector: '[ecOverlay]', template: "<!-- Transcluded Content -->\r\n<ng-content *ngIf=\"displayAsMask || (!displayAsMask && status === 'hasData')\"></ng-content>\r\n<!--Used by GI tests to know the overlay status whether we use ngIf or mask version. No visual impact-->\r\n<span [hidden]=\"true\"\r\n\t class=\"overlay-status-{{status}}\"></span>\r\n<!-- Overlay goes last so it is rendered on top of preceding content due to source order -->\r\n<div *ngIf=\"status !== 'hasData'\"\r\n\t class=\"overlay flex-grow {{overlayClassList}}\"\r\n\t [ngClass]=\"{'not-mask': !displayAsMask,\r\n\t\t\t\t'overlay-error': status === 'error',\r\n\t\t\t\t'overlay-nodata': status === 'noData',\r\n\t\t\t\t'overlay-pending': status === 'pending'}\">\r\n\r\n\t<!--Pending Spinner-->\r\n\t<ec-spinner [hidden]=\"status !== 'pending'\"></ec-spinner>\r\n\r\n\t<ng-template [ngIf]=\"status === 'noData' && noDataTemplate\">\r\n\t\t<ng-container *ngTemplateOutlet=\"noDataTemplate\"></ng-container>\r\n\t</ng-template>\r\n\r\n\t<ng-container *ngIf=\"(status === 'noData' && !noDataTemplate) || status !== 'noData'\">\r\n\t\t<!--Status Message-->\r\n\t\t<div id=\"statusMessage\"\r\n\t\t\t class=\"message\"\r\n\t\t\t *ngIf=\"message\"\r\n\t\t\t [ngClass]=\"{'error': status === 'error', 'mt-1': status === 'pending'}\"\r\n\t\t\t [innerHtml]=\"message | translate\">\r\n\t\t</div>\r\n\r\n\t\t<!-- Action -->\r\n\t\t<ec-button type=\"common\"\r\n\t\t\t\t class=\"mt-3\"\r\n\t\t\t\t *ngIf=\"action?.onClick\"\r\n\t\t\t\t [icon]=\"action?.icon\"\r\n\t\t\t\t (clicked)=\"actionClicked($event)\"\r\n\t\t\t\t [label]=\"action?.label\"\r\n\t\t\t\t [hidden]=\"status === 'pending'\">\r\n\t\t</ec-button>\r\n\t</ng-container>\r\n\r\n</div>", styles: [":host{position:relative}:host(.bg-body)>.overlay{background-color:var(--ec-background-color-body)}:host(.bg-body).is-translucent>.overlay{background-color:var(--ec-background-color-overlay)}:host(.bg-content)>.overlay{background-color:var(--ec-background-color)}:host(.bg-content).is-translucent>.overlay{background-color:var(--ec-background-color-overlay)}.overlay{align-items:center;background-color:var(--ec-overlay-background-color, var(--ec-background-color));display:flex;flex-direction:column;justify-content:center;padding:3rem 4rem;z-index:var(--ec-z-index-overlay);position:absolute;inset:0}.overlay.not-mask{position:relative;min-height:100%}.message{color:var(--ec-color-secondary-dark);font-size:var(--ec-font-size-title)}.message.error{color:var(--ec-color-danger);font-size:var(--ec-font-size-title)}\n"] }]
1919
- }], propDecorators: { status: [{
1920
- type: Input
1921
- }], message: [{
1922
- type: Input
1923
- }], action: [{
1924
- type: Input
1925
- }], noDataTemplate: [{
1926
- type: Input
1927
- }], displayAsMask: [{
1928
- type: Input
1929
- }], overlayClassList: [{
1930
- type: Input
1813
+ NavItemActiveDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: NavItemActiveDirective, deps: [{ token: i1$2.Router }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i1$2.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Directive });
1814
+ NavItemActiveDirectivedir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.9", type: NavItemActiveDirective, selector: "[ecNavItemActive]", inputs: { classValue: ["ecNavItemActive", "classValue"], exact: ["ecNavItemActiveExactMatch", "exact"], queryParams: ["ecNavItemActiveQueryParams", "queryParams"], url: ["ecNavItemActiveUrl", "url"] }, outputs: { routerLinkActivated: "routerLinkActivated" }, ngImport: i0 });
1815
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: NavItemActiveDirective, decorators: [{
1816
+ type: Directive,
1817
+ args: [{
1818
+ selector: '[ecNavItemActive]'
1819
+ }]
1820
+ }], ctorParameters: function () { return [{ type: i1$2.Router }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i1$2.ActivatedRoute }]; }, propDecorators: { classValue: [{
1821
+ type: Input,
1822
+ args: ['ecNavItemActive']
1823
+ }], exact: [{
1824
+ type: Input,
1825
+ args: ['ecNavItemActiveExactMatch']
1826
+ }], queryParams: [{
1827
+ type: Input,
1828
+ args: ['ecNavItemActiveQueryParams']
1829
+ }], url: [{
1830
+ type: Input,
1831
+ args: ['ecNavItemActiveUrl']
1832
+ }], routerLinkActivated: [{
1833
+ type: Output
1931
1834
  }] } });
1932
1835
 
1836
+ ;
1837
+ const menuAnimationSpeed = 350;
1933
1838
  /**
1934
- * Service to help with interfacing with the window object
1935
- * and navigating around the application (going outside of the Angular 2+ router)
1839
+ * Primitive Menu component that encapsulates known templates
1840
+ *
1841
+ * @export
1936
1842
  */
1937
- class WindowService {
1843
+ class MenuComponent {
1938
1844
  /**
1939
- * Tracks if there are any unsaved changes that the user could lose.
1940
- *
1941
- * It is set up as `get` only because it is set with `addNavigateAwayPrompt`.
1942
- *
1943
- * This also includes adding a prompt to the window itself (in addition to
1944
- * working with the `CanDeactivateUnsavedChanges` guard) to cover page reloads
1945
- * which do not trigger router events.
1845
+ * Helper function to return a flat list of all selectable items in the provided menu items. Filters out headings and divided-sections.
1846
+ * This makes it much easier to keep track of currently highlighted items for keyboard navigation.
1946
1847
  */
1947
- get hasUnsavedChanges() {
1948
- return this._hasUnsavedChanges;
1848
+ static getSelectableItems(items) {
1849
+ return items.reduce((selectableItems, item) => {
1850
+ if (item.display !== 'heading' && item.display !== 'divided-section') {
1851
+ selectableItems.push(item);
1852
+ }
1853
+ else if (item.items) {
1854
+ selectableItems.push(...item.items.filter(childItem => childItem.display !== 'heading' && childItem.display !== 'divided-section'));
1855
+ }
1856
+ return selectableItems;
1857
+ }, []);
1949
1858
  }
1950
1859
  /**
1951
- * Expose the innerWidth on the window global. Protects against errors when code
1952
- * is running on a non-browser platform.
1860
+ * Returns an ID for the provided item based on its index in the provided items array. This mimics the behavior of the MenuComponent's
1861
+ * generated IDs for items that don't have provided IDs. This is used in MenuComponent and ComboboxComponent to scroll to specific items.
1862
+ * NOTE: If the items array does not match what is displayed in the menu, this function will not return the correct ID.
1863
+ *
1864
+ * Returns null if the not found
1865
+ * @param items The MenuItems array to search through.
1866
+ * @param item The item to generate the ID for.
1867
+ * @param menuComponentId Used to prefix the generated ID. This should be the ID of the menu component the item is present in.
1868
+ * @memberof MenuComponent
1953
1869
  */
1954
- get innerWidth() {
1955
- return window ? window.innerWidth : undefined;
1870
+ static getIndexedItemId(items, item, menuComponentId) {
1871
+ if (item) {
1872
+ for (let i = 0; i < items.length; i++) {
1873
+ const itemInList = items[i];
1874
+ if (itemInList.label === item.label) {
1875
+ return `${menuComponentId}_item${i}`;
1876
+ }
1877
+ // If the item is a heading or divided section, check its children
1878
+ if (itemInList.items && (itemInList.display === 'heading' || itemInList.display === 'divided-section')) {
1879
+ for (let j = 0; j < itemInList.items.length; j++) {
1880
+ const childItem = itemInList.items[j];
1881
+ // Fall back to checking only the label if the item doesn't have an id
1882
+ if (childItem.label === item.label) {
1883
+ return `${menuComponentId}_item${i}-${j}`;
1884
+ }
1885
+ }
1886
+ }
1887
+ }
1888
+ }
1889
+ return null;
1956
1890
  }
1957
- constructor(router, activatedRoute) {
1958
- this.router = router;
1959
- this.activatedRoute = activatedRoute;
1960
- this._hasUnsavedChanges = false;
1891
+ constructor(el, renderer, windowService, scrollService) {
1892
+ this.el = el;
1893
+ this.renderer = renderer;
1894
+ this.windowService = windowService;
1895
+ this.scrollService = scrollService;
1961
1896
  /**
1962
- * Function called when the window `beforeunload` event is fired.
1897
+ * Array of items to display
1963
1898
  *
1964
- * A reference to the function that was passed to `window.addEventListener`
1965
- * must be retained for `window.removeEventListener` to function properly.
1899
+ * @memberof MenuComponent
1900
+ */
1901
+ this.items = [];
1902
+ /**
1903
+ * Selected item; annotates the item
1904
+ * when displayed with 'selected' class
1966
1905
  *
1967
- * Some browsers require the event's `returnValue` to be set to show the confirmation
1968
- * dialog.
1969
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
1906
+ * @memberof MenuComponent
1970
1907
  */
1971
- this.beforeUnloadFunction = (event) => {
1972
- // Cancel the event as stated by the standard.
1973
- event.preventDefault();
1974
- // Chrome requires returnValue to be set.
1975
- event.returnValue = '';
1976
- };
1977
- if (window) {
1978
- this.resized = fromEvent(window, 'resize');
1979
- }
1980
- }
1981
- /**
1982
- * Navigates to the previous page the user had visited
1983
- */
1984
- goBack() {
1985
- window.history.back();
1908
+ this.selected = null;
1909
+ /**
1910
+ * Display template
1911
+ *
1912
+ * @memberof MenuComponent
1913
+ */
1914
+ this.templateType = 'label';
1915
+ /**
1916
+ * Show message when there are no items
1917
+ */
1918
+ this.showNoItems = false;
1919
+ /**
1920
+ * Text to show when menu is empty and showNoItems is true
1921
+ */
1922
+ this.noDataText = 'NoItems_TC';
1923
+ /**
1924
+ * Controls whether keyboard navigation is enabled
1925
+ */
1926
+ this.enableKeyNav = false;
1927
+ /**
1928
+ * Item currently highlighted by keyboard navigation
1929
+ */
1930
+ this.highlightedItem = null;
1931
+ /**
1932
+ * Tells the menu to maintain the selected/lastSelected item. Turning this off is useful for
1933
+ * action type menus that are displayed on the screen at all times and you do not
1934
+ * want the item to be selected when clicked.
1935
+ */
1936
+ this.maintainSelectedItem = true;
1937
+ /**
1938
+ * Will prevent text-wrapping of menu items and truncate instead. Also turns on a tooltip for the menu item. Default: false;
1939
+ */
1940
+ this.truncateItems = false;
1941
+ /**
1942
+ * When true, the space for the icon is preserved for menu items that do not have icons.
1943
+ * Only applicable for iconAndLabel menus.
1944
+ */
1945
+ this.preserveIconSpace = false;
1946
+ /**
1947
+ * Emitted when `selected` is changed. Emits the referenced object.
1948
+ *
1949
+ * @memberof MenuComponent
1950
+ */
1951
+ this.selectedChanged = new EventEmitter();
1952
+ /**
1953
+ * Emitted when the menu has a parent and back is clicked
1954
+ * @memberof MenuComponent
1955
+ */
1956
+ this.menuClosed = new EventEmitter();
1957
+ /**
1958
+ * Index of the item currently highlighted using keyboard nav
1959
+ */
1960
+ this.highlightedItemIndex = -1;
1961
+ /**
1962
+ * Last item this.selected was set to via selectItem().
1963
+ * This isn't necessarily the same as this.selected, because this.selected is an input property
1964
+ * and could have been changed by a consumer through some means other than selectItem().
1965
+ * This allows us to prevent double-calls to selectItem() with the same input.
1966
+ */
1967
+ this.lastSelected = null;
1968
+ /**
1969
+ * Flattened array of all selectable items in the menu. Makes it easier to keep track of the currently highlighted item for keyboard navigation.
1970
+ */
1971
+ this.selectableItems = [];
1986
1972
  }
1987
- /**An abstraction around the browsers window history length.
1988
- * Returns zero if unable to access or running outside a browser context
1989
- */
1990
- getHistoryLength() {
1991
- return window?.history?.length || 0;
1973
+ ngOnChanges(changes) {
1974
+ if (changes.items && this.items) {
1975
+ this.selectableItems = MenuComponent.getSelectableItems(this.items);
1976
+ }
1992
1977
  }
1993
1978
  /**
1994
- * Navigate to any url you know the path to
1995
- * @param url The URL to navigate to
1979
+ * Sets & displays the interalized template based on
1980
+ * the set template.
1981
+ * @see { @link https://angular.io/guide/lifecycle-hooks|Angular Lifecycle Hooks}
1996
1982
  *
1997
- * @deprecated For legacy support only; use `router.navigateByUrl` instead
1983
+ * @memberof MenuComponent
1998
1984
  */
1999
- async navigateToUrl(url) {
2000
- try {
2001
- if (url.indexOf('/app/') === 0) {
2002
- await this.router.navigateByUrl(url.substring(5));
2003
- }
2004
- else {
2005
- await this.router.navigateByUrl(url);
2006
- }
1985
+ ngAfterContentInit() {
1986
+ switch (this.templateType) {
1987
+ case ("label"):
1988
+ this.internalizedTemplate = this.iconAndLabelTemplate;
1989
+ break;
1990
+ case ("iconAndLabel"):
1991
+ this.internalizedTemplate = this.iconAndLabelTemplate;
1992
+ break;
1993
+ case ("checkAndLabel"):
1994
+ this.internalizedTemplate = this.checkAndLabelTemplate;
1995
+ break;
1996
+ case ("iconLabelCaption"):
1997
+ this.internalizedTemplate = this.iconLabelCaptionTemplate;
1998
+ break;
1999
+ default:
2000
+ throw new Error(`Invalid templateType for MenuComponent. Please use either: 'label', 'iconAndLabel' or 'checkAndLabel'`);
2007
2001
  }
2008
- catch (e) {
2009
- // If the router throws we will try to navigate to the fully qualified url as a last ditch effort.
2010
- // This can happen if we missed a link that needs to be converted to ng5 or our ng1Href directive
2011
- // didn't handle a link correctly
2012
- window.location.href = url;
2002
+ //if the consumer provided a menuItemTemplate, override the internalizedTemplate with that.
2003
+ if (this.customMenuTemplate) {
2004
+ this.internalizedTemplate = this.customMenuTemplate;
2013
2005
  }
2006
+ if (this.id) {
2007
+ this.attrId = this.id;
2008
+ }
2009
+ this.setItemIds();
2010
+ if (this.highlightedItem && this.selectableItems.length) {
2011
+ this.highlightedItemIndex = this.selectableItems.findIndex(item => { return this.highlightedItem === item; });
2012
+ }
2013
+ this.addKeydownListener();
2014
2014
  }
2015
- /**
2016
- * Adds a `beforeunload` function to the window to alert the user that there are about to leave
2017
- * the current page and ask if they'd like to leave or stay
2018
- */
2019
- addNavigateAwayPrompt() {
2020
- this._hasUnsavedChanges = true;
2021
- window.addEventListener("beforeunload", this.beforeUnloadFunction);
2022
- }
2023
- /**
2024
- * Removes the `beforeunload` function added to the window
2025
- */
2026
- removeNavigateAwayPrompt() {
2027
- this._hasUnsavedChanges = false;
2028
- window.removeEventListener("beforeunload", this.beforeUnloadFunction);
2029
- }
2030
- /**
2031
- * Send data to another window.
2032
- *
2033
- * __SECURITY RISK__ - Always use a specific target origin. Failing to provide a specific target origin can allow
2034
- * malicious sites to receive the message.
2035
- *
2036
- * @param targetWindow - Window to send the message to
2037
- * @param message - Data to send
2038
- * @param targetOrigin - What the URI of the target window must be for the message to be sent.
2039
- * If sending data to another EnergyCAP window, this should always be `window.location.origin` to ensure
2040
- * that only instances of EnergyCAP app receive the message.
2041
- */
2042
- postMessage(targetWindow, message, targetOrigin) {
2043
- targetWindow.postMessage(message, targetOrigin);
2044
- }
2045
- /**
2046
- * Open a new window
2047
- * @param url - The URL of the resource to be loaded
2048
- */
2049
- openNew(url) {
2050
- window.open(url, '_blank');
2015
+ ngOnDestroy() {
2016
+ // Remove the listener when the component is destroyed
2017
+ if (this.removeKeydownListener) {
2018
+ this.removeKeydownListener();
2019
+ }
2051
2020
  }
2052
2021
  /**
2053
- * A wrapper around the router for changing the query params for the current url
2054
- * without creating a new history entry or removing any existing query parameters.
2055
- * The provided params are updated if they already exist or added to the url if they don't
2022
+ * When a menu item is selected, open a child menu if the item has items, call
2023
+ * the item's click method if defined, or emit the selected item.
2056
2024
  *
2057
- * @returns a promise that resolves to true if the navigation succeeds, false if something (like a guard) blocks it.
2058
- * In normal use, the navigation should succeed unless we use query params to block access to a route the user is already on
2059
- */
2060
- async modifyHistoryQueryParamsSubset(queryParams) {
2061
- return this.router.navigate([], {
2062
- relativeTo: this.activatedRoute,
2063
- replaceUrl: true,
2064
- queryParams: queryParams,
2065
- queryParamsHandling: 'merge',
2066
- });
2067
- }
2068
- /**A wrapper around the default javascript confirm dialog to allow us to unit test dependent code.
2069
- * Of course eventually we'd like to have pretty confirmations for everything, but in some cases it wasn't worth the extra time
2070
- * so we're using this instead.
2025
+ * @param item The selected item
2026
+ * @memberof MenuComponent
2071
2027
  */
2072
- confirm(prompt) {
2073
- return Promise.resolve(confirm(prompt));
2028
+ selectItem(event, item, isKeyEvent) {
2029
+ event.stopPropagation();
2030
+ //In the case that the user clicks an item, selectItem() will be called from the click handler
2031
+ //and through onRouterLinkActivated. Only one of these will make it through this if statement
2032
+ //because the first one will set this.lastSelected = item.
2033
+ if (!item.disabled && !item.readonly && this.lastSelected !== item) {
2034
+ if (!item.url) {
2035
+ if (item.onClick) {
2036
+ item.onClick(item, false);
2037
+ }
2038
+ if (item.items && item.display !== 'heading' && item.display !== 'divided-section') {
2039
+ this.toggleChildMenu(true);
2040
+ }
2041
+ else {
2042
+ this.onSelection(item);
2043
+ }
2044
+ // We need to manually handle the url navigation if the keyboard was used
2045
+ }
2046
+ else if (isKeyEvent || event.target?.tagName === 'LI') {
2047
+ if (item.target) {
2048
+ window.open(item.url, item.target);
2049
+ }
2050
+ else {
2051
+ this.windowService.navigateToUrl(item.url);
2052
+ }
2053
+ // Emit so upstream components know an item was selected
2054
+ this.onSelection(item);
2055
+ }
2056
+ else {
2057
+ this.onSelection(item);
2058
+ }
2059
+ if (this.maintainSelectedItem) {
2060
+ this.selected = item;
2061
+ this.lastSelected = item;
2062
+ }
2063
+ else {
2064
+ this.selected = null;
2065
+ this.lastSelected = null;
2066
+ }
2067
+ }
2074
2068
  }
2075
2069
  /**
2076
- * Close the current window or a window instance if one is provided
2077
- * @param windowInstance - Window to close (optional)
2070
+ * Close the current menu and open the parent menu
2071
+ * @memberof MenuComponent
2078
2072
  */
2079
- closeWindow(windowInstance) {
2080
- if (windowInstance) {
2081
- windowInstance.close();
2082
- }
2083
- else {
2084
- window.close();
2073
+ back(event) {
2074
+ event.stopPropagation();
2075
+ if (this.parent && this.parent.onClick) {
2076
+ this.parent.onClick(null, true);
2085
2077
  }
2086
- }
2087
- getLocation() {
2088
- return window.location.pathname + window.location.hash;
2089
- }
2090
- /** Get the current value of the full url, including protocol, host and path */
2091
- getFullUrl() {
2092
- return window.location.href;
2093
- }
2094
- /** Get the current value of the base url, including protocol, domain and port (if explicitly specified) */
2095
- getBaseUrl() {
2096
- return window.location.origin;
2078
+ this.menuClosed.emit();
2097
2079
  }
2098
2080
  /**
2099
- * Reloads the browser window.
2100
- * NOT RECOMMENDED. Seek other options for reloading content within Angular before resorting to this.
2081
+ * Emit the selected item
2082
+ * @param item The selected item
2101
2083
  */
2102
- reloadWindow() {
2103
- window.location.reload();
2084
+ onSelection(item) {
2085
+ if (item.display !== 'heading' && item.display !== 'divided-section') {
2086
+ this.selectedChanged.emit(item);
2087
+ }
2104
2088
  }
2105
- }
2106
- WindowService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: WindowService, deps: [{ token: i1$2.Router }, { token: i1$2.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Injectable });
2107
- WindowService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: WindowService, providedIn: 'root' });
2108
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: WindowService, decorators: [{
2109
- type: Injectable,
2110
- args: [{
2111
- providedIn: 'root'
2112
- }]
2113
- }], ctorParameters: function () { return [{ type: i1$2.Router }, { type: i1$2.ActivatedRoute }]; } });
2114
-
2115
- class NavItemActiveDirective {
2116
2089
  /**
2117
- * Determines whether the directive will try to make an exact match on the url or not
2118
- * If false, the directive will add the active class if the first part of the url matches
2119
- * the active route.
2120
- * see: https://angular.io/api/router/Router#isactive
2090
+ * Open or close the child menu. When the child menu closes, the selected
2091
+ * item is reset.
2092
+ * @memberof MenuComponent
2121
2093
  */
2122
- set exact(value) {
2123
- if (value === undefined) {
2124
- this._exact = true;
2094
+ toggleChildMenu(open) {
2095
+ let navEl = this.el.nativeElement.querySelector('nav');
2096
+ if (open) {
2097
+ // Remove the listener on the parent menu when a child menu is opened
2098
+ // This is to avoid interference between the parent and child menus
2099
+ if (this.removeKeydownListener) {
2100
+ this.removeKeydownListener();
2101
+ }
2102
+ let height = navEl.offsetHeight;
2103
+ let width = navEl.offsetWidth;
2104
+ // In order to animate the child menu, we need to set height on the nav element
2105
+ // so we can absolutely position the two menus and maintain the current menu's height
2106
+ this.renderer.setStyle(navEl, 'height', `${height}px`);
2107
+ this.renderer.setStyle(navEl, 'width', `${width}px`);
2108
+ this.renderer.addClass(this.el.nativeElement, 'open');
2109
+ setTimeout(() => {
2110
+ this.renderer.addClass(this.el.nativeElement, 'open-active');
2111
+ });
2125
2112
  }
2126
2113
  else {
2127
- this._exact = value;
2114
+ // Re-add the listener once the child menu closes
2115
+ this.addKeydownListener();
2116
+ this.renderer.removeClass(this.el.nativeElement, 'open-active');
2117
+ setTimeout(() => {
2118
+ this.renderer.removeClass(this.el.nativeElement, 'open');
2119
+ // Reset the nav element's height to auto
2120
+ this.renderer.setStyle(navEl, 'height', '100%');
2121
+ this.selected = null;
2122
+ }, menuAnimationSpeed);
2128
2123
  }
2129
- this.update();
2130
2124
  }
2131
2125
  /**
2132
- * The url of the NavItem to check for activeness. Convert the item url into a
2133
- * UrlTree relative to the ActivatedRoute so router#isActive works even with relative urls.
2134
- * See Angular's [routerLink](https://github.com/angular/angular/blob/8282e15c2becbe42a49befa07d6407247e8243d8/packages/router/src/directives/router_link.ts#L249)
2135
- * and [routerLinkActive](https://github.com/angular/angular/blob/8282e15c2becbe42a49befa07d6407247e8243d8/packages/router/src/directives/router_link_active.ts#L139)
2136
- * for a similiar implementation.
2126
+ * Handle key presses to navigate the menu
2137
2127
  */
2138
- set url(value) {
2139
- if (value !== null && value !== undefined) {
2140
- this._url = this.router.createUrlTree([value], { relativeTo: this.route, queryParams: this.queryParams });
2141
- this.update();
2142
- }
2143
- }
2144
- constructor(router, el, renderer, route) {
2145
- this.router = router;
2146
- this.el = el;
2147
- this.renderer = renderer;
2148
- this.route = route;
2149
- this._exact = true;
2150
- /** Emits when the url becomes active */
2151
- this.routerLinkActivated = new EventEmitter();
2152
- /** Subject that emits when component is destroyed to unsubscribe from any subscriptions */
2153
- this.destroyed = new Subject();
2154
- }
2155
- /** Check if url is active on NavigationEnd events */
2156
- ngOnInit() {
2157
- this.router.events.pipe(takeUntil(this.destroyed), filter(e => e instanceof NavigationEnd)).subscribe(() => {
2158
- this.update();
2159
- });
2160
- }
2161
- ngOnDestroy() {
2162
- this.destroyed.next();
2163
- this.destroyed.unsubscribe();
2164
- }
2165
- /** If url is active apply the defined class to the element, otherwise remove it */
2166
- update() {
2167
- if (this._url && this.classValue) {
2168
- if (this.router.isActive(this._url, { matrixParams: 'ignored', queryParams: this._exact ? 'exact' : 'subset', paths: this._exact ? 'exact' : 'subset', fragment: 'ignored' })) {
2169
- this.renderer.addClass(this.el.nativeElement, this.classValue);
2170
- this.routerLinkActivated.emit(new Event('routerLinkActivated'));
2171
- }
2172
- else {
2173
- this.renderer.removeClass(this.el.nativeElement, this.classValue);
2128
+ keyNavigate(event) {
2129
+ if (this.enableKeyNav && event.target === this.dropdownToggleButton?.nativeElement) {
2130
+ switch (event.key) {
2131
+ case 'ArrowUp':
2132
+ case 'Up':
2133
+ case 'ArrowDown':
2134
+ case 'Down':
2135
+ event.stopPropagation();
2136
+ event.preventDefault();
2137
+ this.moveHighlightedUpOrDown(event);
2138
+ break;
2139
+ case 'ArrowRight':
2140
+ case 'Right':
2141
+ event.stopPropagation();
2142
+ event.preventDefault();
2143
+ // Select the item if it has child items
2144
+ if (this.highlightedItem && this.highlightedItem.items) {
2145
+ this.selectItem(event, this.highlightedItem, true);
2146
+ }
2147
+ break;
2148
+ case 'ArrowLeft':
2149
+ case 'Left':
2150
+ event.stopPropagation();
2151
+ event.preventDefault();
2152
+ // Close the menu if it is a child menu
2153
+ if (this.parent) {
2154
+ this.back(event);
2155
+ }
2156
+ break;
2157
+ case ' ':
2158
+ case 'Spacebar':
2159
+ case 'Enter':
2160
+ // Prevent 'enter' from doing whatever it does on the currently focused element
2161
+ event.preventDefault();
2162
+ if (this.highlightedItemIndex > -1 && this.highlightedItem) {
2163
+ this.selectItem(event, this.highlightedItem, true);
2164
+ // If the header is highlighted
2165
+ }
2166
+ else if (this.highlightedItemIndex === -1) {
2167
+ // Close the menu if it's a child
2168
+ if (this.parent) {
2169
+ this.back(event);
2170
+ }
2171
+ }
2172
+ break;
2173
+ default:
2174
+ return;
2174
2175
  }
2175
2176
  }
2176
2177
  }
2178
+ /**
2179
+ * Scroll to the item currently marked as 'is-selected'. Wait a tick for the
2180
+ * NgClassDirecitve or NavItemActiveDirective to respond to the model changes
2181
+ * and update the view.
2182
+ */
2183
+ scrollToSelectedItem() {
2184
+ window.setTimeout(() => {
2185
+ const linkSelector = `li.is-selected`;
2186
+ this.scrollService.scrollToItem(`#${this.id}_list`, linkSelector);
2187
+ });
2188
+ }
2189
+ moveHighlightedUpOrDown(event) {
2190
+ switch (event.key) {
2191
+ case 'ArrowUp':
2192
+ case 'Up':
2193
+ if (this.highlightedItemIndex > -1) {
2194
+ this.highlightedItemIndex--;
2195
+ }
2196
+ break;
2197
+ case 'ArrowDown':
2198
+ case 'Down':
2199
+ if (this.highlightedItemIndex < this.selectableItems.length - 1) {
2200
+ this.highlightedItemIndex++;
2201
+ }
2202
+ break;
2203
+ default:
2204
+ return;
2205
+ }
2206
+ if (this.highlightedItemIndex > -1) {
2207
+ // Store the item at the current highlight index
2208
+ this.highlightedItem = this.selectableItems[this.highlightedItemIndex];
2209
+ }
2210
+ else {
2211
+ this.highlightedItem = null;
2212
+ }
2213
+ this.scrollToHighlightedItem();
2214
+ }
2215
+ /**
2216
+ * Scroll to the specified menu item.
2217
+ * If no item is provided, it will scroll to the first item.
2218
+ *
2219
+ * @param item The menu item to scroll to.
2220
+ * @memberof MenuComponent
2221
+ */
2222
+ scrollMenu(item) {
2223
+ if (this.items.length > 0 && this.id) {
2224
+ item = item ? item : this.items[0];
2225
+ let itemId = item.id ? item.id : MenuComponent.getIndexedItemId(this.items, item, this.id);
2226
+ this.scrollService.scrollItemCentered(`#${this.id}_list`, `#${itemId}`);
2227
+ }
2228
+ }
2229
+ scrollToHighlightedItem() {
2230
+ this.scrollMenu(this.highlightedItem);
2231
+ }
2232
+ addKeydownListener() {
2233
+ // Only attempt to add the listener if keyboard nav is enabled
2234
+ if (this.enableKeyNav) {
2235
+ // renderer.listen adds the listener and returns a function to remove it from the renderer.
2236
+ // The listener remains active until this function is called.
2237
+ this.removeKeydownListener = this.renderer.listen('document', 'keydown', (event) => this.keyNavigate(event));
2238
+ }
2239
+ }
2240
+ /**
2241
+ * Sets the menu item ids using its index if item doesn't already have one
2242
+ */
2243
+ setItemIds() {
2244
+ if (this.items) {
2245
+ this.items.forEach((item, index) => {
2246
+ item.id = item.id ? item.id : this.id + '_item' + index;
2247
+ });
2248
+ }
2249
+ }
2177
2250
  }
2178
- NavItemActiveDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: NavItemActiveDirective, deps: [{ token: i1$2.Router }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i1$2.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Directive });
2179
- NavItemActiveDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.9", type: NavItemActiveDirective, selector: "[ecNavItemActive]", inputs: { classValue: ["ecNavItemActive", "classValue"], exact: ["ecNavItemActiveExactMatch", "exact"], queryParams: ["ecNavItemActiveQueryParams", "queryParams"], url: ["ecNavItemActiveUrl", "url"] }, outputs: { routerLinkActivated: "routerLinkActivated" }, ngImport: i0 });
2180
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: NavItemActiveDirective, decorators: [{
2181
- type: Directive,
2182
- args: [{
2183
- selector: '[ecNavItemActive]'
2184
- }]
2185
- }], ctorParameters: function () { return [{ type: i1$2.Router }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i1$2.ActivatedRoute }]; }, propDecorators: { classValue: [{
2186
- type: Input,
2187
- args: ['ecNavItemActive']
2188
- }], exact: [{
2189
- type: Input,
2190
- args: ['ecNavItemActiveExactMatch']
2191
- }], queryParams: [{
2192
- type: Input,
2193
- args: ['ecNavItemActiveQueryParams']
2194
- }], url: [{
2195
- type: Input,
2196
- args: ['ecNavItemActiveUrl']
2197
- }], routerLinkActivated: [{
2251
+ MenuComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: WindowService }, { token: ScrollService }], target: i0.ɵɵFactoryTarget.Component });
2252
+ MenuComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: MenuComponent, selector: "ec-menu", inputs: { id: "id", items: "items", selected: "selected", parent: "parent", templateType: "templateType", customMenuTemplate: "customMenuTemplate", title: "title", showNoItems: "showNoItems", noDataText: "noDataText", enableKeyNav: "enableKeyNav", highlightedItem: "highlightedItem", maintainSelectedItem: "maintainSelectedItem", truncateItems: "truncateItems", preserveIconSpace: "preserveIconSpace", dropdownToggleButton: "dropdownToggleButton" }, outputs: { selectedChanged: "selectedChanged", menuClosed: "menuClosed" }, host: { properties: { "attr.id": "this.attrId" } }, viewQueries: [{ propertyName: "labelTemplate", first: true, predicate: ["label"], descendants: true, static: true }, { propertyName: "iconAndLabelTemplate", first: true, predicate: ["iconAndLabel"], descendants: true, static: true }, { propertyName: "checkAndLabelTemplate", first: true, predicate: ["checkAndLabel"], descendants: true, static: true }, { propertyName: "iconLabelCaptionTemplate", first: true, predicate: ["iconLabelCaption"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<nav>\r\n <div class=\"parent\"\r\n [class.no-data]=\"showNoItems && (!items || items.length === 0)\">\r\n <header id=\"{{id}}_header\"\r\n class=\"text-heading-3 p-1\"\r\n [class.is-selected]=\"highlightedItemIndex === -1\"\r\n *ngIf=\"parent\"\r\n (click)=\"back($event)\">\r\n <div class=\"item-wrapper\">\r\n <i class=\"ec-icon icon-angle-down rotate-90 flex-shrink\"></i>\r\n <span class=\"label text-truncate flex-grow\">{{parent?.label}}</span>\r\n </div>\r\n </header>\r\n\r\n <ul id=\"{{id}}_list\"\r\n class=\"py-1\">\r\n <ng-container *ngFor=\"let item of items; index as i\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: {$implicit: item, index: i}\"></ng-container>\r\n\r\n <!-- Show child items under parent item if the item is a heading or divided-section -->\r\n <ng-container *ngIf=\"item.items?.length && (item.display === 'heading' || item.display === 'divided-section')\">\r\n <ng-container *ngFor=\"let childItem of item.items; index as j; first as isFirst; last as isLast\"\r\n [ngTemplateOutlet]=\"itemTemplate\"\r\n [ngTemplateOutletContext]=\"{$implicit: childItem, index: i + '-' + j, isDividedSectionChild: item.display === 'divided-section', isFirst: isFirst, isLast: isLast}\">\r\n </ng-container>\r\n </ng-container>\r\n </ng-container>\r\n </ul>\r\n\r\n <p class=\"no-data-message\">{{noDataText | translate}}</p>\r\n </div>\r\n\r\n <!-- Child menu (Rendered to the right) -->\r\n <ec-menu *ngIf=\"selected?.items && selected?.display !== 'heading' && selected?.display !== 'divided-section'\"\r\n id=\"{{id}}_child\"\r\n class=\"child\"\r\n [parent]=\"selected\"\r\n [items]=\"selected?.items\"\r\n [showNoItems]=\"true\"\r\n [templateType]=\"templateType\"\r\n [enableKeyNav]=\"true\"\r\n [truncateItems]=\"truncateItems\"\r\n (selectedChanged)=\"onSelection($event)\"\r\n (menuClosed)=\"toggleChildMenu(false)\">\r\n </ec-menu>\r\n</nav>\r\n\r\n<ng-template #itemTemplate\r\n let-item\r\n let-i=\"index\"\r\n let-isDividedSectionChild=\"isDividedSectionChild\"\r\n let-isFirst=\"isFirst\"\r\n let-isLast=\"isLast\">\r\n <li *ngIf=\"!(item.hideIfNoItems && !item.items?.length) && item.display !== 'divided-section'\"\r\n id=\"{{item.id || id + '_item' + i}}\"\r\n class=\"{{item.display || 'item'}} {{item.classList}}\"\r\n [class.divider-top]=\"item.display === 'divider-top' || (isDividedSectionChild && isFirst)\"\r\n [class.divider]=\"item.display === 'divider' || (isDividedSectionChild && isLast)\"\r\n [attr.disabled]=\"item.disabled\"\r\n [hidden]=\"item.hidden\"\r\n ecNavItemActive=\"is-selected\"\r\n [ecNavItemActiveQueryParams]=\"item.queryParams\"\r\n [ecNavItemActiveUrl]=\"item.url\"\r\n [ecNavItemActiveExactMatch]='item.isActiveExactMatch'\r\n (routerLinkActivated)=\"selectItem($event, item)\"\r\n [ngClass]=\"{'is-highlighted':(selected === item && item?.display !== 'heading') || highlightedItem === item, 'is-link': item.url, 'is-disabled': item.disabled, 'is-readonly': item.readonly, 'is-checked': item.checked, 'text-heading-3': item?.display === 'heading'}\"\r\n (click)=\"selectItem($event, item)\">\r\n\r\n <a *ngIf=\"item.url && !item.externalLink\"\r\n id=\"{{item.id}}_link\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\"\r\n [routerLink]=\"item.url\"\r\n [queryParams]=\"item.queryParams || null\"\r\n target=\"{{item.target || '_self'}}\">\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </a>\r\n\r\n <a *ngIf=\"item.url && item.externalLink\"\r\n id=\"{{item.id}}_link\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\"\r\n href=\"{{item.url}}\"\r\n target=\"{{item.target || '_self'}}\">\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </a>\r\n\r\n <div *ngIf=\"!item.url\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\">\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </div>\r\n </li>\r\n</ng-template>\r\n\r\n<!-- 'label' Item Template -->\r\n<ng-template #label\r\n let-item>\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items && item.display !== 'heading' && item.display !== 'divided-section'\"></i>\r\n</ng-template>\r\n\r\n<!-- 'checkAndLabel' Item Template -->\r\n<ng-template #checkAndLabel\r\n let-item>\r\n\r\n <i class=\"ec-icon icon-check ec-icon-sm\"\r\n *ngIf=\"item.display !== 'heading'\"></i>\r\n\r\n <i class=\"ec-icon {{item.icon}} ml-2\"\r\n *ngIf=\"item.icon\"></i>\r\n\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items && item.display !== 'heading' && item.display !== 'divided-section'\"></i>\r\n</ng-template>\r\n\r\n<!-- 'iconAndLabel' Item Template -->\r\n<ng-template #iconAndLabel\r\n let-item>\r\n <!-- If menuItem.icon exists and is not blank, show the icon in the menu -->\r\n <i class=\"ec-icon {{item.icon}}\"\r\n *ngIf=\"(item.icon && item.icon !== '') || preserveIconSpace\"></i>\r\n\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items && item.display !== 'heading' && item.display !== 'divided-section'\"></i>\r\n</ng-template>\r\n\r\n<ng-template #iconLabelCaption\r\n let-item>\r\n <i class=\"ec-icon {{item.icon}}\"\r\n *ngIf=\"(item.icon && item.icon !== '') || preserveIconSpace\"></i>\r\n <div *ngIf=\"item.display !== 'heading'\"\r\n class=\"label flex-grow\">\r\n <div id=\"{{item.id}}_label\"\r\n class=\"text-body-1\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</div>\r\n <div id=\"{{item.id}}_caption\"\r\n *ngIf=\"item.caption\"\r\n class=\"text-caption-1\"\r\n [class.text-truncate]=\"truncateItems\">{{item.caption}}</div>\r\n </div>\r\n <h3 *ngIf=\"item.display === 'heading'\"\r\n class=\"flex-grow text-heading-3 align-self-center\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</h3>\r\n <i class=\"ec-icon icon-angle-down rotate-270 align-self-center\"\r\n *ngIf=\"item?.items && item.display !== 'heading' && item.display !== 'divided-section'\"></i>\r\n</ng-template>", styles: ["@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(1turn)}}:host{display:block;font-size:var(--ec-menu-font-size, var(--ec-font-size-action));font-weight:400;background-color:var(--ec-menu-background-color, var(--ec-background-color))}:host.open>nav>.parent,:host.open>nav>.child{position:absolute;left:0;top:0;right:0;height:100%;transition:transform .25s ease}:host.open>nav>.parent{transform:translate(0)}:host.open>nav>.child{transform:translate(100%)}:host.open-active>nav>.parent{transform:translate(-100%)}:host.open-active>nav>.child{transform:translate(0)}:host(.bg-transparent){background-color:transparent}:host-context(.is-always-open){height:100%}:host-context(.is-always-open) .item-wrapper{padding-left:1rem;padding-right:1rem}nav{display:flex;position:relative;height:100%;overflow:hidden}.parent{display:flex;flex-direction:column;flex:auto;position:relative;max-width:100%}.parent>header{cursor:pointer}.parent>header.is-selected .item-wrapper,.parent>header.is-highlighted .item-wrapper{background-color:var(--ec-background-color-selected)}.parent>header:hover .item-wrapper{background-color:var(--ec-background-color-hover)}.parent.no-data ul{display:none}.parent.no-data .no-data-message{display:block}ul{padding:0;margin:0;list-style:none;flex:auto;height:100%;overflow-y:auto}ul li{cursor:pointer;padding:0 .25rem}ul li a{color:inherit;border-bottom:0;text-decoration:none}ul li.is-selected .item-wrapper,ul li.is-highlighted .item-wrapper{background-color:var(--ec-background-color-selected)}ul li:hover .item-wrapper{background-color:var(--ec-background-color-hover)}ul li:focus .item-wrapper{outline:none;background-color:var(--ec-color-disabled-dark);position:relative;z-index:1}ul li.is-disabled .item-wrapper{color:var(--ec-color-disabled-dark);opacity:var(--ec-form-control-opacity-disabled)}ul li.is-disabled,ul li.is-readonly{cursor:default}ul li.is-disabled .item-wrapper,ul li.is-readonly .item-wrapper{background-color:transparent;color:inherit}ul li.is-checked .icon-check{opacity:1}ul li.heading{cursor:default}ul li.heading .item-wrapper{background-color:transparent}ul li.heading:not(:first-child){margin-top:.5rem}ul li.divider:not(:last-child){border-bottom:1px solid var(--ec-border-color);padding-bottom:.25rem;margin-bottom:.25rem}ul li.divider-top:not(:first-child):not(.divider + .divider-top){border-top:1px solid var(--ec-border-color);padding-top:.25rem;margin-top:.25rem}ul li.indent-1 .item-wrapper{padding-left:1.5rem}ul li.indent-2 .item-wrapper{padding-left:2.5rem}ul li.indent-3 .item-wrapper{padding-left:3.5rem}.item-wrapper{cursor:inherit;line-height:1.25rem;min-height:1.75rem;padding:.25rem .5rem;border-radius:var(--ec-border-radius);display:flex;color:inherit}.item-wrapper .label{margin-right:auto}.item-wrapper .label+.ec-icon{margin-left:.5rem}.item-wrapper .ec-icon{margin-top:calc((1.25rem - var(--ec-font-size-icon)) / 2);flex:none}.item-wrapper .ec-icon+.label{margin-left:.5rem}.item-wrapper .ec-icon-sm{margin-top:calc((1.25rem - calc(var(--ec-font-size-icon) * .75)) / 2)}.item-wrapper .icon-check{opacity:0}.no-data-message{display:none;text-align:center;padding:1rem;color:var(--ec-color-hint-dark);margin-bottom:0;font-size:var(--ec-font-size-body)}:host-context(ec-tree) ul{overflow-x:hidden}:host-context(ec-tree) li.is-selected,:host-context(ec-tree) li.is-highlighted{font-weight:700;color:var(--ec-menu-color-highlighted, inherit)}:host-context(ec-tree) li.is-selected:not(:hover),:host-context(ec-tree) li.is-highlighted:not(:hover){background-color:transparent}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: NavItemActiveDirective, selector: "[ecNavItemActive]", inputs: ["ecNavItemActive", "ecNavItemActiveExactMatch", "ecNavItemActiveQueryParams", "ecNavItemActiveUrl"], outputs: ["routerLinkActivated"] }, { kind: "component", type: MenuComponent, selector: "ec-menu", inputs: ["id", "items", "selected", "parent", "templateType", "customMenuTemplate", "title", "showNoItems", "noDataText", "enableKeyNav", "highlightedItem", "maintainSelectedItem", "truncateItems", "preserveIconSpace", "dropdownToggleButton"], outputs: ["selectedChanged", "menuClosed"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }] });
2253
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuComponent, decorators: [{
2254
+ type: Component,
2255
+ args: [{ selector: 'ec-menu', template: "<nav>\r\n <div class=\"parent\"\r\n [class.no-data]=\"showNoItems && (!items || items.length === 0)\">\r\n <header id=\"{{id}}_header\"\r\n class=\"text-heading-3 p-1\"\r\n [class.is-selected]=\"highlightedItemIndex === -1\"\r\n *ngIf=\"parent\"\r\n (click)=\"back($event)\">\r\n <div class=\"item-wrapper\">\r\n <i class=\"ec-icon icon-angle-down rotate-90 flex-shrink\"></i>\r\n <span class=\"label text-truncate flex-grow\">{{parent?.label}}</span>\r\n </div>\r\n </header>\r\n\r\n <ul id=\"{{id}}_list\"\r\n class=\"py-1\">\r\n <ng-container *ngFor=\"let item of items; index as i\">\r\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: {$implicit: item, index: i}\"></ng-container>\r\n\r\n <!-- Show child items under parent item if the item is a heading or divided-section -->\r\n <ng-container *ngIf=\"item.items?.length && (item.display === 'heading' || item.display === 'divided-section')\">\r\n <ng-container *ngFor=\"let childItem of item.items; index as j; first as isFirst; last as isLast\"\r\n [ngTemplateOutlet]=\"itemTemplate\"\r\n [ngTemplateOutletContext]=\"{$implicit: childItem, index: i + '-' + j, isDividedSectionChild: item.display === 'divided-section', isFirst: isFirst, isLast: isLast}\">\r\n </ng-container>\r\n </ng-container>\r\n </ng-container>\r\n </ul>\r\n\r\n <p class=\"no-data-message\">{{noDataText | translate}}</p>\r\n </div>\r\n\r\n <!-- Child menu (Rendered to the right) -->\r\n <ec-menu *ngIf=\"selected?.items && selected?.display !== 'heading' && selected?.display !== 'divided-section'\"\r\n id=\"{{id}}_child\"\r\n class=\"child\"\r\n [parent]=\"selected\"\r\n [items]=\"selected?.items\"\r\n [showNoItems]=\"true\"\r\n [templateType]=\"templateType\"\r\n [enableKeyNav]=\"true\"\r\n [truncateItems]=\"truncateItems\"\r\n (selectedChanged)=\"onSelection($event)\"\r\n (menuClosed)=\"toggleChildMenu(false)\">\r\n </ec-menu>\r\n</nav>\r\n\r\n<ng-template #itemTemplate\r\n let-item\r\n let-i=\"index\"\r\n let-isDividedSectionChild=\"isDividedSectionChild\"\r\n let-isFirst=\"isFirst\"\r\n let-isLast=\"isLast\">\r\n <li *ngIf=\"!(item.hideIfNoItems && !item.items?.length) && item.display !== 'divided-section'\"\r\n id=\"{{item.id || id + '_item' + i}}\"\r\n class=\"{{item.display || 'item'}} {{item.classList}}\"\r\n [class.divider-top]=\"item.display === 'divider-top' || (isDividedSectionChild && isFirst)\"\r\n [class.divider]=\"item.display === 'divider' || (isDividedSectionChild && isLast)\"\r\n [attr.disabled]=\"item.disabled\"\r\n [hidden]=\"item.hidden\"\r\n ecNavItemActive=\"is-selected\"\r\n [ecNavItemActiveQueryParams]=\"item.queryParams\"\r\n [ecNavItemActiveUrl]=\"item.url\"\r\n [ecNavItemActiveExactMatch]='item.isActiveExactMatch'\r\n (routerLinkActivated)=\"selectItem($event, item)\"\r\n [ngClass]=\"{'is-highlighted':(selected === item && item?.display !== 'heading') || highlightedItem === item, 'is-link': item.url, 'is-disabled': item.disabled, 'is-readonly': item.readonly, 'is-checked': item.checked, 'text-heading-3': item?.display === 'heading'}\"\r\n (click)=\"selectItem($event, item)\">\r\n\r\n <a *ngIf=\"item.url && !item.externalLink\"\r\n id=\"{{item.id}}_link\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\"\r\n [routerLink]=\"item.url\"\r\n [queryParams]=\"item.queryParams || null\"\r\n target=\"{{item.target || '_self'}}\">\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </a>\r\n\r\n <a *ngIf=\"item.url && item.externalLink\"\r\n id=\"{{item.id}}_link\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\"\r\n href=\"{{item.url}}\"\r\n target=\"{{item.target || '_self'}}\">\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </a>\r\n\r\n <div *ngIf=\"!item.url\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\">\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </div>\r\n </li>\r\n</ng-template>\r\n\r\n<!-- 'label' Item Template -->\r\n<ng-template #label\r\n let-item>\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items && item.display !== 'heading' && item.display !== 'divided-section'\"></i>\r\n</ng-template>\r\n\r\n<!-- 'checkAndLabel' Item Template -->\r\n<ng-template #checkAndLabel\r\n let-item>\r\n\r\n <i class=\"ec-icon icon-check ec-icon-sm\"\r\n *ngIf=\"item.display !== 'heading'\"></i>\r\n\r\n <i class=\"ec-icon {{item.icon}} ml-2\"\r\n *ngIf=\"item.icon\"></i>\r\n\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items && item.display !== 'heading' && item.display !== 'divided-section'\"></i>\r\n</ng-template>\r\n\r\n<!-- 'iconAndLabel' Item Template -->\r\n<ng-template #iconAndLabel\r\n let-item>\r\n <!-- If menuItem.icon exists and is not blank, show the icon in the menu -->\r\n <i class=\"ec-icon {{item.icon}}\"\r\n *ngIf=\"(item.icon && item.icon !== '') || preserveIconSpace\"></i>\r\n\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items && item.display !== 'heading' && item.display !== 'divided-section'\"></i>\r\n</ng-template>\r\n\r\n<ng-template #iconLabelCaption\r\n let-item>\r\n <i class=\"ec-icon {{item.icon}}\"\r\n *ngIf=\"(item.icon && item.icon !== '') || preserveIconSpace\"></i>\r\n <div *ngIf=\"item.display !== 'heading'\"\r\n class=\"label flex-grow\">\r\n <div id=\"{{item.id}}_label\"\r\n class=\"text-body-1\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</div>\r\n <div id=\"{{item.id}}_caption\"\r\n *ngIf=\"item.caption\"\r\n class=\"text-caption-1\"\r\n [class.text-truncate]=\"truncateItems\">{{item.caption}}</div>\r\n </div>\r\n <h3 *ngIf=\"item.display === 'heading'\"\r\n class=\"flex-grow text-heading-3 align-self-center\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</h3>\r\n <i class=\"ec-icon icon-angle-down rotate-270 align-self-center\"\r\n *ngIf=\"item?.items && item.display !== 'heading' && item.display !== 'divided-section'\"></i>\r\n</ng-template>", styles: ["@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(1turn)}}:host{display:block;font-size:var(--ec-menu-font-size, var(--ec-font-size-action));font-weight:400;background-color:var(--ec-menu-background-color, var(--ec-background-color))}:host.open>nav>.parent,:host.open>nav>.child{position:absolute;left:0;top:0;right:0;height:100%;transition:transform .25s ease}:host.open>nav>.parent{transform:translate(0)}:host.open>nav>.child{transform:translate(100%)}:host.open-active>nav>.parent{transform:translate(-100%)}:host.open-active>nav>.child{transform:translate(0)}:host(.bg-transparent){background-color:transparent}:host-context(.is-always-open){height:100%}:host-context(.is-always-open) .item-wrapper{padding-left:1rem;padding-right:1rem}nav{display:flex;position:relative;height:100%;overflow:hidden}.parent{display:flex;flex-direction:column;flex:auto;position:relative;max-width:100%}.parent>header{cursor:pointer}.parent>header.is-selected .item-wrapper,.parent>header.is-highlighted .item-wrapper{background-color:var(--ec-background-color-selected)}.parent>header:hover .item-wrapper{background-color:var(--ec-background-color-hover)}.parent.no-data ul{display:none}.parent.no-data .no-data-message{display:block}ul{padding:0;margin:0;list-style:none;flex:auto;height:100%;overflow-y:auto}ul li{cursor:pointer;padding:0 .25rem}ul li a{color:inherit;border-bottom:0;text-decoration:none}ul li.is-selected .item-wrapper,ul li.is-highlighted .item-wrapper{background-color:var(--ec-background-color-selected)}ul li:hover .item-wrapper{background-color:var(--ec-background-color-hover)}ul li:focus .item-wrapper{outline:none;background-color:var(--ec-color-disabled-dark);position:relative;z-index:1}ul li.is-disabled .item-wrapper{color:var(--ec-color-disabled-dark);opacity:var(--ec-form-control-opacity-disabled)}ul li.is-disabled,ul li.is-readonly{cursor:default}ul li.is-disabled .item-wrapper,ul li.is-readonly .item-wrapper{background-color:transparent;color:inherit}ul li.is-checked .icon-check{opacity:1}ul li.heading{cursor:default}ul li.heading .item-wrapper{background-color:transparent}ul li.heading:not(:first-child){margin-top:.5rem}ul li.divider:not(:last-child){border-bottom:1px solid var(--ec-border-color);padding-bottom:.25rem;margin-bottom:.25rem}ul li.divider-top:not(:first-child):not(.divider + .divider-top){border-top:1px solid var(--ec-border-color);padding-top:.25rem;margin-top:.25rem}ul li.indent-1 .item-wrapper{padding-left:1.5rem}ul li.indent-2 .item-wrapper{padding-left:2.5rem}ul li.indent-3 .item-wrapper{padding-left:3.5rem}.item-wrapper{cursor:inherit;line-height:1.25rem;min-height:1.75rem;padding:.25rem .5rem;border-radius:var(--ec-border-radius);display:flex;color:inherit}.item-wrapper .label{margin-right:auto}.item-wrapper .label+.ec-icon{margin-left:.5rem}.item-wrapper .ec-icon{margin-top:calc((1.25rem - var(--ec-font-size-icon)) / 2);flex:none}.item-wrapper .ec-icon+.label{margin-left:.5rem}.item-wrapper .ec-icon-sm{margin-top:calc((1.25rem - calc(var(--ec-font-size-icon) * .75)) / 2)}.item-wrapper .icon-check{opacity:0}.no-data-message{display:none;text-align:center;padding:1rem;color:var(--ec-color-hint-dark);margin-bottom:0;font-size:var(--ec-font-size-body)}:host-context(ec-tree) ul{overflow-x:hidden}:host-context(ec-tree) li.is-selected,:host-context(ec-tree) li.is-highlighted{font-weight:700;color:var(--ec-menu-color-highlighted, inherit)}:host-context(ec-tree) li.is-selected:not(:hover),:host-context(ec-tree) li.is-highlighted:not(:hover){background-color:transparent}\n"] }]
2256
+ }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: WindowService }, { type: ScrollService }]; }, propDecorators: { id: [{
2257
+ type: Input
2258
+ }], attrId: [{
2259
+ type: HostBinding,
2260
+ args: ['attr.id']
2261
+ }], items: [{
2262
+ type: Input
2263
+ }], selected: [{
2264
+ type: Input
2265
+ }], parent: [{
2266
+ type: Input
2267
+ }], templateType: [{
2268
+ type: Input
2269
+ }], customMenuTemplate: [{
2270
+ type: Input
2271
+ }], title: [{
2272
+ type: Input
2273
+ }], showNoItems: [{
2274
+ type: Input
2275
+ }], noDataText: [{
2276
+ type: Input
2277
+ }], enableKeyNav: [{
2278
+ type: Input
2279
+ }], highlightedItem: [{
2280
+ type: Input
2281
+ }], maintainSelectedItem: [{
2282
+ type: Input
2283
+ }], truncateItems: [{
2284
+ type: Input
2285
+ }], preserveIconSpace: [{
2286
+ type: Input
2287
+ }], dropdownToggleButton: [{
2288
+ type: Input
2289
+ }], selectedChanged: [{
2198
2290
  type: Output
2291
+ }], menuClosed: [{
2292
+ type: Output
2293
+ }], labelTemplate: [{
2294
+ type: ViewChild,
2295
+ args: ['label', { static: true }]
2296
+ }], iconAndLabelTemplate: [{
2297
+ type: ViewChild,
2298
+ args: ['iconAndLabel', { static: true }]
2299
+ }], checkAndLabelTemplate: [{
2300
+ type: ViewChild,
2301
+ args: ['checkAndLabel', { static: true }]
2302
+ }], iconLabelCaptionTemplate: [{
2303
+ type: ViewChild,
2304
+ args: ['iconLabelCaption', { static: true }]
2199
2305
  }] } });
2200
2306
 
2201
- ;
2202
- const menuAnimationSpeed = 350;
2203
2307
  /**
2204
- * Primitive Menu component that encapsulates known templates
2308
+ * Primitive directive that popups a container using PopperJS
2205
2309
  *
2206
2310
  * @export
2207
2311
  */
2208
- class MenuComponent {
2209
- constructor(el, renderer, windowService, scrollService) {
2210
- this.el = el;
2312
+ class PopupContainerDirective {
2313
+ /**
2314
+ * Creates an instance of PopupContainerDirective.
2315
+ * @param templateRef Reference to the popup template
2316
+ * @param viewContainer Reference to the view container
2317
+ * @param document Reference to Document
2318
+ * @memberof PopupContainerDirective
2319
+ */
2320
+ constructor(templateRef, viewContainer, document, renderer) {
2321
+ this.templateRef = templateRef;
2322
+ this.viewContainer = viewContainer;
2323
+ this.document = document;
2211
2324
  this.renderer = renderer;
2212
- this.windowService = windowService;
2213
- this.scrollService = scrollService;
2214
- /**
2215
- * Array of items to display
2216
- *
2217
- * @memberof MenuComponent
2218
- */
2219
- this.items = [];
2220
- /**
2221
- * Selected item; annotates the item
2222
- * when displayed with 'selected' class
2223
- *
2224
- * @memberof MenuComponent
2225
- */
2226
- this.selected = null;
2227
- /**
2228
- * Display template
2229
- *
2230
- * @memberof MenuComponent
2231
- */
2232
- this.templateType = 'label';
2233
2325
  /**
2234
- * Show message when there are no items
2235
- */
2236
- this.showNoItems = false;
2237
- /**
2238
- * Text to show when menu is empty and showNoItems is true
2239
- */
2240
- this.noDataText = 'NoItems_TC';
2241
- /**
2242
- * Controls whether keyboard navigation is enabled
2243
- */
2244
- this.enableKeyNav = false;
2245
- /**
2246
- * Item currently highlighted by keyboard navigation
2247
- */
2248
- this.highlightedItem = null;
2249
- /**
2250
- * Tells the menu to maintain the selected/lastSelected item. Turning this off is useful for
2251
- * action type menus that are displayed on the screen at all times and you do not
2252
- * want the item to be selected when clicked.
2253
- */
2254
- this.maintainSelectedItem = true;
2255
- /**
2256
- * Will prevent text-wrapping of menu items and truncate instead. Also turns on a tooltip for the menu item. Default: false;
2257
- */
2258
- this.truncateItems = false;
2259
- /**
2260
- * When true, the space for the icon is preserved for menu items that do not have icons.
2261
- * Only applicable for iconAndLabel menus.
2262
- */
2263
- this.preserveIconSpace = false;
2264
- /**
2265
- * Emitted when `selected` is changed. Emits the referenced object.
2266
- *
2267
- * @memberof MenuComponent
2268
- */
2269
- this.selectedChanged = new EventEmitter();
2270
- /**
2271
- * Emitted when the menu has a parent and back is clicked
2272
- * @memberof MenuComponent
2273
- */
2274
- this.menuClosed = new EventEmitter();
2275
- /**
2276
- * Index of the item currently highlighted using keyboard nav
2277
- */
2278
- this.highlightedItemIndex = -1;
2279
- /**
2280
- * Last item this.selected was set to via selectItem().
2281
- * This isn't necessarily the same as this.selected, because this.selected is an input property
2282
- * and could have been changed by a consumer through some means other than selectItem().
2283
- * This allows us to prevent double-calls to selectItem() with the same input.
2326
+ * Emit the {@link PopupStatus} when it changes
2284
2327
  */
2285
- this.lastSelected = null;
2328
+ this.popperStatusChange = new EventEmitter();
2286
2329
  }
2287
2330
  /**
2288
- * Sets & displays the interalized template based on
2289
- * the set template.
2290
- * @see { @link https://angular.io/guide/lifecycle-hooks|Angular Lifecycle Hooks}
2331
+ * Angular onInit lifecycle hook
2332
+ * @see https://angular.io/guide/lifecycle-hooks
2333
+ */
2334
+ ngOnInit() {
2335
+ this.templateViewRef = this.viewContainer.createEmbeddedView(this.templateRef);
2336
+ }
2337
+ /**
2338
+ * Angular onDestroy lifecycle hook. Close and delete references. Unsubscribe observables
2339
+ * @see https://angular.io/guide/lifecycle-hooks
2340
+ */
2341
+ ngOnDestroy() {
2342
+ this.hide();
2343
+ }
2344
+ /**
2345
+ * Displays the templateRef as a popup
2291
2346
  *
2292
- * @memberof MenuComponent
2347
+ * @memberof PopupContainerDirective
2293
2348
  */
2294
- ngAfterContentInit() {
2295
- switch (this.templateType) {
2296
- case ("label"):
2297
- this.internalizedTemplate = this.iconAndLabelTemplate;
2298
- break;
2299
- case ("iconAndLabel"):
2300
- this.internalizedTemplate = this.iconAndLabelTemplate;
2301
- break;
2302
- case ("checkAndLabel"):
2303
- this.internalizedTemplate = this.checkAndLabelTemplate;
2304
- break;
2305
- case ("iconLabelCaption"):
2306
- this.internalizedTemplate = this.iconLabelCaptionTemplate;
2307
- break;
2308
- default:
2309
- throw new Error(`Invalid templateType for MenuComponent. Please use either: 'label', 'iconAndLabel' or 'checkAndLabel'`);
2310
- }
2311
- //if the consumer provided a menuItemTemplate, override the internalizedTemplate with that.
2312
- if (this.customMenuTemplate) {
2313
- this.internalizedTemplate = this.customMenuTemplate;
2349
+ show() {
2350
+ if (PopupContainerDirective.GlobalPopupRef) {
2351
+ if (PopupContainerDirective.GlobalPopupRef != this) {
2352
+ PopupContainerDirective.GlobalPopupRef.hide();
2353
+ PopupContainerDirective.GlobalPopupRef = undefined;
2354
+ }
2314
2355
  }
2315
- if (this.id) {
2316
- this.attrId = this.id;
2356
+ if (!this.globalCloseSubscription) {
2357
+ this.globalCloseSubscription = fromEvent(this.document.body, "click").subscribe((event) => {
2358
+ this.hide();
2359
+ });
2317
2360
  }
2318
- this.setItemIds();
2319
- if (this.highlightedItem && this.items.length) {
2320
- this.highlightedItemIndex = this.items.findIndex(item => { return this.highlightedItem === item; });
2361
+ if (!this.popperRef) {
2362
+ // Add the popper template as an embedded view since PopperJS
2363
+ // manipulates DOM elements.
2364
+ this.popupViewRef = this.viewContainer.createEmbeddedView(this.popup);
2365
+ // Since popper needs real DOM elements, grab the first non-comment
2366
+ // DOM element to use as our anchor.
2367
+ let anchorElement = this.popupViewRef.rootNodes.find(elem => { return elem.nodeName !== "#text"; });
2368
+ // Use the parents elements as our DOM elements to Popper
2369
+ this.popperRef = new Popper(this.templateViewRef.rootNodes[0], anchorElement, this.popperOptions);
2370
+ PopupContainerDirective.GlobalPopupRef = this;
2371
+ this.popperStatusChange.emit('visible');
2321
2372
  }
2322
- this.addKeydownListener();
2323
2373
  }
2324
- ngOnDestroy() {
2325
- // Remove the listener when the component is destroyed
2326
- if (this.removeKeydownListener) {
2327
- this.removeKeydownListener();
2374
+ /**
2375
+ * Hides the templateRef
2376
+ *
2377
+ * @memberof PopupContainerDirective
2378
+ */
2379
+ hide() {
2380
+ if (this.globalCloseSubscription) {
2381
+ this.globalCloseSubscription.unsubscribe();
2382
+ this.globalCloseSubscription = undefined;
2383
+ }
2384
+ if (this.popperRef && this.popupViewRef) {
2385
+ this.popupViewRef.destroy();
2386
+ this.popperRef.destroy();
2387
+ this.popperRef = undefined;
2388
+ this.popperStatusChange.emit('hidden');
2328
2389
  }
2329
2390
  }
2330
2391
  /**
2331
- * When a menu item is selected, open a child menu if the item has items, call
2332
- * the item's click method if defined, or emit the selected item.
2333
- *
2334
- * @param item The selected item
2335
- * @memberof MenuComponent
2392
+ * Updates the popup container position
2336
2393
  */
2337
- selectItem(event, item, isKeyEvent) {
2338
- event.stopPropagation();
2339
- //In the case that the user clicks an item, selectItem() will be called from the click handler
2340
- //and through onRouterLinkActivated. Only one of these will make it through this if statement
2341
- //because the first one will set this.lastSelected = item.
2342
- if (!item.disabled && !item.readonly && this.lastSelected !== item) {
2343
- if (!item.url) {
2344
- if (item.onClick) {
2345
- item.onClick(item, false);
2346
- }
2347
- if (item.items) {
2348
- this.toggleChildMenu(true);
2349
- }
2350
- else {
2351
- this.onSelection(item);
2394
+ update() {
2395
+ if (this.popperRef) {
2396
+ this.popperRef.update();
2397
+ }
2398
+ }
2399
+ fixPosition(minWidthNone, appendToBody = false) {
2400
+ if (this.popperRef && this.popperRef['reference'] && this.popperRef['popper']) {
2401
+ let popupEl = this.popperRef['popper'];
2402
+ // Reset width style previously assigned because the content may have
2403
+ // changed and the auto width would be different
2404
+ this.renderer.removeStyle(popupEl, 'width');
2405
+ this.renderer.setStyle(popupEl, 'position', 'fixed');
2406
+ if (appendToBody) {
2407
+ const bodyEl = this.document.querySelector('body');
2408
+ const popupParent = this.renderer.parentNode(popupEl);
2409
+ if (popupParent !== bodyEl) {
2410
+ this.renderer.appendChild(bodyEl, popupEl);
2352
2411
  }
2353
- // We need to manually handle the url navigation if the keyboard was used
2354
2412
  }
2355
- else if (isKeyEvent || event.target?.tagName === 'LI') {
2356
- if (item.target) {
2357
- window.open(item.url, item.target);
2413
+ let toggleEl = this.popperRef['reference'];
2414
+ let width = popupEl.offsetWidth;
2415
+ let boundaries = popupEl.getBoundingClientRect();
2416
+ let left = boundaries.left;
2417
+ let coords = toggleEl.getBoundingClientRect();
2418
+ // Set the top of our menu to the bottom of the toggle element
2419
+ let top = coords.bottom;
2420
+ if (this.popperOptions && this.popperOptions.placement) {
2421
+ if (this.popperOptions.placement === 'bottom-start' || this.popperOptions.placement === 'top-start') {
2422
+ left = coords.left;
2358
2423
  }
2359
2424
  else {
2360
- this.windowService.navigateToUrl(item.url);
2425
+ left = coords.right - ((minWidthNone || width > coords.width) ? width : coords.width);
2361
2426
  }
2362
- // Emit so upstream components know an item was selected
2363
- this.onSelection(item);
2364
- }
2365
- else {
2366
- this.onSelection(item);
2367
2427
  }
2368
- if (this.maintainSelectedItem) {
2369
- this.selected = item;
2370
- this.lastSelected = item;
2428
+ // if it won't fit (with 10px space before hitting the window edge), flip it
2429
+ if (boundaries.height + top + 10 > window.innerHeight) {
2430
+ top = coords.top - boundaries.height;
2371
2431
  }
2372
- else {
2373
- this.selected = null;
2374
- this.lastSelected = null;
2432
+ this.renderer.setStyle(popupEl, 'transform', 'none');
2433
+ this.renderer.setStyle(popupEl, 'left', left + 'px');
2434
+ this.renderer.setStyle(popupEl, 'top', top + 'px');
2435
+ this.renderer.setStyle(popupEl, 'width', width + 'px');
2436
+ if (!minWidthNone) {
2437
+ this.renderer.setStyle(popupEl, 'min-width', coords.width + 'px');
2375
2438
  }
2376
2439
  }
2377
2440
  }
2378
- /**
2379
- * Close the current menu and open the parent menu
2380
- * @memberof MenuComponent
2381
- */
2382
- back(event) {
2383
- event.stopPropagation();
2384
- if (this.parent && this.parent.onClick) {
2385
- this.parent.onClick(null, true);
2441
+ }
2442
+ /**
2443
+ * Global reference to the currently displayed popup; only
2444
+ * one popup directive can be in `show` state at a given time.
2445
+ *
2446
+ * @memberof PopupContainerDirective
2447
+ */
2448
+ PopupContainerDirective.GlobalPopupRef = undefined;
2449
+ PopupContainerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: PopupContainerDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: DOCUMENT }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
2450
+ PopupContainerDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.9", type: PopupContainerDirective, selector: "[ecPopup]", inputs: { popup: ["ecPopup", "popup"], popperOptions: ["options", "popperOptions"] }, ngImport: i0 });
2451
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: PopupContainerDirective, decorators: [{
2452
+ type: Directive,
2453
+ args: [{ selector: '[ecPopup]' }]
2454
+ }], ctorParameters: function () { return [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: undefined, decorators: [{
2455
+ type: Inject,
2456
+ args: [DOCUMENT]
2457
+ }] }, { type: i0.Renderer2 }]; }, propDecorators: { popup: [{
2458
+ type: Input,
2459
+ args: ['ecPopup']
2460
+ }], popperOptions: [{
2461
+ type: Input,
2462
+ args: ['options']
2463
+ }] } });
2464
+
2465
+ /** Advanced validation for textbox form controls */
2466
+ const textboxValidation = (validatorParams) => {
2467
+ return (control) => {
2468
+ let validators = [];
2469
+ // Innocent until proven guilty
2470
+ validatorParams.valid = true;
2471
+ if (validatorParams.required) {
2472
+ validators.push(Validators.required);
2386
2473
  }
2387
- this.menuClosed.emit();
2388
- }
2389
- /**
2390
- * Emit the selected item
2391
- * @param item The selected item
2392
- */
2393
- onSelection(item) {
2394
- if (item.display !== 'heading') {
2395
- this.selectedChanged.emit(item);
2474
+ if (validatorParams.minLength !== undefined) {
2475
+ validators.push(Validators.minLength(validatorParams.minLength));
2396
2476
  }
2397
- }
2398
- /**
2399
- * Open or close the child menu. When the child menu closes, the selected
2400
- * item is reset.
2401
- * @memberof MenuComponent
2402
- */
2403
- toggleChildMenu(open) {
2404
- let navEl = this.el.nativeElement.querySelector('nav');
2405
- if (open) {
2406
- // Remove the listener on the parent menu when a child menu is opened
2407
- // This is to avoid interference between the parent and child menus
2408
- if (this.removeKeydownListener) {
2409
- this.removeKeydownListener();
2410
- }
2411
- let height = navEl.offsetHeight;
2412
- let width = navEl.offsetWidth;
2413
- // In order to animate the child menu, we need to set height on the nav element
2414
- // so we can absolutely position the two menus and maintain the current menu's height
2415
- this.renderer.setStyle(navEl, 'height', `${height}px`);
2416
- this.renderer.setStyle(navEl, 'width', `${width}px`);
2417
- this.renderer.addClass(this.el.nativeElement, 'open');
2418
- setTimeout(() => {
2419
- this.renderer.addClass(this.el.nativeElement, 'open-active');
2420
- });
2477
+ if (validatorParams.maxLength !== undefined) {
2478
+ validators.push(Validators.maxLength(validatorParams.maxLength));
2421
2479
  }
2422
- else {
2423
- // Re-add the listener once the child menu closes
2424
- this.addKeydownListener();
2425
- this.renderer.removeClass(this.el.nativeElement, 'open-active');
2426
- setTimeout(() => {
2427
- this.renderer.removeClass(this.el.nativeElement, 'open');
2428
- // Reset the nav element's height to auto
2429
- this.renderer.setStyle(navEl, 'height', '100%');
2430
- this.selected = null;
2431
- }, menuAnimationSpeed);
2480
+ if (validatorParams.pattern !== undefined) {
2481
+ validators.push(Validators.pattern(validatorParams.pattern));
2432
2482
  }
2433
- }
2434
- /**
2435
- * Handle key presses to navigate the menu
2436
- */
2437
- keyNavigate(event) {
2438
- if (this.enableKeyNav && event.target === this.dropdownToggleButton?.nativeElement) {
2439
- switch (event.key) {
2440
- case 'ArrowUp':
2441
- case 'Up':
2442
- case 'ArrowDown':
2443
- case 'Down':
2444
- event.stopPropagation();
2445
- event.preventDefault();
2446
- this.moveHighlightedUpOrDown(event);
2447
- break;
2448
- case 'ArrowRight':
2449
- case 'Right':
2450
- event.stopPropagation();
2451
- event.preventDefault();
2452
- // Select the item if it has child items
2453
- if (this.highlightedItem && this.highlightedItem.items) {
2454
- this.selectItem(event, this.highlightedItem, true);
2455
- }
2456
- break;
2457
- case 'ArrowLeft':
2458
- case 'Left':
2459
- event.stopPropagation();
2460
- event.preventDefault();
2461
- // Close the menu if it is a child menu
2462
- if (this.parent) {
2463
- this.back(event);
2464
- }
2465
- break;
2466
- case ' ':
2467
- case 'Spacebar':
2468
- case 'Enter':
2469
- // Prevent 'enter' from doing whatever it does on the currently focused element
2470
- event.preventDefault();
2471
- if (this.highlightedItemIndex > -1 && this.highlightedItem) {
2472
- this.selectItem(event, this.highlightedItem, true);
2473
- // If the header is highlighted
2474
- }
2475
- else if (this.highlightedItemIndex === -1) {
2476
- // Close the menu if it's a child
2477
- if (this.parent) {
2478
- this.back(event);
2479
- }
2480
- }
2481
- break;
2482
- default:
2483
- return;
2483
+ validators.forEach(validator => {
2484
+ let validationResult = validator(control);
2485
+ if (validationResult) {
2486
+ validatorParams.valid = false;
2484
2487
  }
2488
+ });
2489
+ if (validatorParams.valid) {
2490
+ return null;
2491
+ }
2492
+ else {
2493
+ return {
2494
+ textbox: validatorParams
2495
+ };
2485
2496
  }
2497
+ };
2498
+ };
2499
+ const phoneNumberValidationPattern = '^\\s*(?:\\+?(\\d{1,3}))?[-. (]*(\\d{3})[-. )]*(\\d{3})[-. ]*(\\d{4})(?: *x(\\d+))?\\s*$';
2500
+ const urlValidationPattern = '([A-Za-z])+(:\/\/)+[^\\s]*';
2501
+ class TextboxComponent extends FormControlBase {
2502
+ constructor(validationMessageService, formGroupHelper, translate) {
2503
+ super(validationMessageService, formGroupHelper);
2504
+ this.validationMessageService = validationMessageService;
2505
+ this.formGroupHelper = formGroupHelper;
2506
+ this.translate = translate;
2507
+ /**
2508
+ * Set the value of the input's autocomplete attribute
2509
+ */
2510
+ this.autocomplete = 'off';
2511
+ /**
2512
+ * The textbox type
2513
+ */
2514
+ this.type = "text";
2515
+ /**
2516
+ * The value of the rows attribute for a textarea. Only applies to multi-line type
2517
+ */
2518
+ this.rows = 3;
2519
+ /**
2520
+ * If set to true, we will select all text within the input if
2521
+ * autofocus is also set to true
2522
+ */
2523
+ this.selectOnAutofocus = false;
2524
+ /**
2525
+ * If set to true, we will upper case on focus out
2526
+ */
2527
+ this.upperCase = false;
2528
+ /**
2529
+ * Validation pattern for the input. This is determined on the input type specified
2530
+ */
2531
+ this.validationPattern = '';
2532
+ }
2533
+ ngOnChanges(changes) {
2534
+ super.ngOnChanges(changes);
2486
2535
  }
2487
2536
  /**
2488
- * Scroll to the item currently marked as 'is-selected'. Wait a tick for the
2489
- * NgClassDirecitve or NavItemActiveDirective to respond to the model changes
2490
- * and update the view.
2537
+ * The angular onInit lifecycle hook
2491
2538
  */
2492
- scrollToSelectedItem() {
2493
- window.setTimeout(() => {
2494
- const linkSelector = `li.is-selected`;
2495
- this.scrollService.scrollToItem(`#${this.id}_list`, linkSelector);
2496
- });
2497
- }
2498
- moveHighlightedUpOrDown(event) {
2499
- switch (event.key) {
2500
- case 'ArrowUp':
2501
- case 'Up':
2502
- if (this.highlightedItemIndex > -1) {
2503
- this.highlightedItemIndex--;
2504
- // Skip any in-menu heading items
2505
- if (this.highlightedItemIndex > -1 && this.items[this.highlightedItemIndex].display === 'heading') {
2506
- this.highlightedItemIndex--;
2507
- }
2508
- }
2509
- break;
2510
- case 'ArrowDown':
2511
- case 'Down':
2512
- if (this.highlightedItemIndex < this.items.length - 1) {
2513
- this.highlightedItemIndex++;
2514
- // Skip any in-menu heading items
2515
- if (this.highlightedItemIndex < this.items.length - 1 && this.items[this.highlightedItemIndex].display === 'heading') {
2516
- this.highlightedItemIndex++;
2517
- }
2518
- }
2519
- break;
2520
- default:
2521
- return;
2539
+ ngOnInit() {
2540
+ super.ngOnInit();
2541
+ this.validationPattern = '';
2542
+ if (this.type === 'tel') {
2543
+ this.validationPattern = phoneNumberValidationPattern;
2522
2544
  }
2523
- if (this.highlightedItemIndex > -1) {
2524
- // Store the item at the current highlight index
2525
- this.highlightedItem = this.items[this.highlightedItemIndex];
2545
+ else if (this.type === 'url') {
2546
+ this.validationPattern = urlValidationPattern;
2526
2547
  }
2527
- else {
2528
- this.highlightedItem = null;
2548
+ if (this.placeholder) {
2549
+ this.translate.get(this.placeholder)
2550
+ .subscribe((translated) => {
2551
+ this.placeholder = translated;
2552
+ });
2529
2553
  }
2530
- this.scrollToHighlightedItem();
2531
2554
  }
2532
2555
  /**
2533
- * Scroll to the specified menu item.
2534
- * If no item is provided, it will scroll to the first item.
2535
- *
2536
- * @param item The menu item to scroll to.
2537
- * @memberof MenuComponent
2556
+ * The angular afterViewInit lifecycle hook
2538
2557
  */
2539
- scrollMenu(item) {
2540
- if (this.items.length > 0) {
2541
- item = item ? item : this.items[0];
2542
- let itemIndex = this.findItemIndex(item);
2543
- if (this.id) {
2544
- let itemSelector = item.id ? `#${item.id}` : `#${this.id}_item${itemIndex}`;
2545
- this.scrollService.scrollItemCentered(`#${this.id}_list`, itemSelector);
2546
- }
2558
+ ngAfterViewInit() {
2559
+ if (this.autofocus) {
2560
+ this.setFocus(this.selectOnAutofocus);
2547
2561
  }
2548
2562
  }
2549
- scrollToHighlightedItem() {
2550
- this.scrollMenu(this.highlightedItem);
2551
- }
2552
2563
  /**
2553
- * Find a given item's index in the filtered items array.
2554
- *
2555
- * Returns -1 if not found
2556
- * @param itemToFind The matching item to find in the items array.
2557
- * @memberof MenuComponent
2564
+ * Function to set focus on an input programmatically after the page
2565
+ * had been rendered. The highlight text flag will select the text
2566
+ * within the input if passed in and true
2558
2567
  */
2559
- findItemIndex(itemToFind) {
2560
- if (itemToFind) {
2561
- return this.items.findIndex(item => {
2562
- return item.label === itemToFind.label;
2563
- });
2564
- }
2565
- else {
2566
- return -1;
2567
- }
2568
- }
2569
- addKeydownListener() {
2570
- // Only attempt to add the listener if keyboard nav is enabled
2571
- if (this.enableKeyNav) {
2572
- // renderer.listen adds the listener and returns a function to remove it from the renderer.
2573
- // The listener remains active until this function is called.
2574
- this.removeKeydownListener = this.renderer.listen('document', 'keydown', (event) => this.keyNavigate(event));
2568
+ setFocus(highlightText) {
2569
+ this.inputElement.nativeElement.focus();
2570
+ if (highlightText) {
2571
+ this.inputElement.nativeElement.select();
2575
2572
  }
2576
2573
  }
2577
2574
  /**
2578
- * Sets the menu item ids using its index if item doesn't already have one
2579
- */
2580
- setItemIds() {
2581
- if (this.items) {
2582
- this.items.forEach((item, index) => {
2583
- item.id = item.id ? item.id : this.id + '_item' + index;
2584
- });
2575
+ * Focus out event handler
2576
+ * will upper case and trim value if upperCase is true (this is what we do on the apis)
2577
+ */
2578
+ focusOutEvent() {
2579
+ if (this.upperCase && this.formModel.value) {
2580
+ this.formModel.setValue(this.formModel.value.toUpperCase().trim());
2585
2581
  }
2586
2582
  }
2587
2583
  }
2588
- MenuComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: WindowService }, { token: ScrollService }], target: i0.ɵɵFactoryTarget.Component });
2589
- MenuComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: MenuComponent, selector: "ec-menu", inputs: { id: "id", items: "items", selected: "selected", parent: "parent", templateType: "templateType", customMenuTemplate: "customMenuTemplate", title: "title", showNoItems: "showNoItems", noDataText: "noDataText", enableKeyNav: "enableKeyNav", highlightedItem: "highlightedItem", maintainSelectedItem: "maintainSelectedItem", truncateItems: "truncateItems", preserveIconSpace: "preserveIconSpace", dropdownToggleButton: "dropdownToggleButton" }, outputs: { selectedChanged: "selectedChanged", menuClosed: "menuClosed" }, host: { properties: { "attr.id": "this.attrId" } }, viewQueries: [{ propertyName: "labelTemplate", first: true, predicate: ["label"], descendants: true, static: true }, { propertyName: "iconAndLabelTemplate", first: true, predicate: ["iconAndLabel"], descendants: true, static: true }, { propertyName: "checkAndLabelTemplate", first: true, predicate: ["checkAndLabel"], descendants: true, static: true }, { propertyName: "iconLabelCaptionTemplate", first: true, predicate: ["iconLabelCaption"], descendants: true, static: true }], ngImport: i0, template: "<nav>\r\n <div class=\"parent\"\r\n [class.no-data]=\"showNoItems && (!items || items.length === 0)\">\r\n <header id=\"{{id}}_header\"\r\n class=\"text-heading-3 p-1\"\r\n [class.is-selected]=\"highlightedItemIndex === -1\"\r\n *ngIf=\"parent\"\r\n (click)=\"back($event)\">\r\n <div class=\"item-wrapper\">\r\n <i class=\"ec-icon icon-angle-down rotate-90 flex-shrink\"></i>\r\n <span class=\"label text-truncate flex-grow\">{{parent?.label}}</span>\r\n </div>\r\n </header>\r\n\r\n <ul id=\"{{id}}_list\"\r\n class=\"py-1\">\r\n <li *ngFor=\"let item of items; index as i\"\r\n id=\"{{item.id || id + '_item' + i}}\"\r\n class=\"{{item.display || 'item'}} {{item.classList}}\"\r\n [attr.disabled]=\"item.disabled\"\r\n [hidden]=\"item.hidden\"\r\n ecNavItemActive=\"is-selected\"\r\n [ecNavItemActiveQueryParams]=\"item.queryParams\"\r\n [ecNavItemActiveUrl]=\"item.url\"\r\n [ecNavItemActiveExactMatch]='item.isActiveExactMatch'\r\n (routerLinkActivated)=\"selectItem($event, item)\"\r\n [ngClass]=\"{'is-highlighted':(selected === item && item?.display !== 'heading') || highlightedItem === item, 'is-link': item.url, 'is-disabled': item.disabled, 'is-readonly': item.readonly, 'is-checked': item.checked, 'text-heading-3': item?.display === 'heading'}\"\r\n (click)=\"selectItem($event, item)\">\r\n\r\n <a *ngIf=\"item.url && !item.externalLink\"\r\n id=\"{{item.id}}_link\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\"\r\n [routerLink]=\"item.url\"\r\n [queryParams]=\"item.queryParams || null\"\r\n target=\"{{item.target || '_self'}}\">\r\n\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </a>\r\n\r\n <a *ngIf=\"item.url && item.externalLink\"\r\n id=\"{{item.id}}_link\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\"\r\n href=\"{{item.url}}\"\r\n target=\"{{item.target || '_self'}}\">\r\n\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </a>\r\n\r\n <div *ngIf=\"!item.url\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\">\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </div>\r\n </li>\r\n </ul>\r\n\r\n <p class=\"no-data-message\">{{noDataText | translate}}</p>\r\n </div>\r\n\r\n <!-- Child menu (Rendered to the right) -->\r\n <ec-menu *ngIf=\"selected?.items\"\r\n id=\"{{id}}_child\"\r\n class=\"child\"\r\n [parent]=\"selected\"\r\n [items]=\"selected?.items\"\r\n [showNoItems]=\"true\"\r\n [templateType]=\"templateType\"\r\n [enableKeyNav]=\"true\"\r\n [truncateItems]=\"truncateItems\"\r\n (selectedChanged)=\"onSelection($event)\"\r\n (menuClosed)=\"toggleChildMenu(false)\">\r\n </ec-menu>\r\n</nav>\r\n\r\n<!-- 'label' Item Template -->\r\n<ng-template #label\r\n let-item>\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items\"></i>\r\n</ng-template>\r\n\r\n<!-- 'checkAndLabel' Item Template -->\r\n<ng-template #checkAndLabel\r\n let-item>\r\n\r\n <i class=\"ec-icon icon-check ec-icon-sm\"\r\n *ngIf=\"item.display !== 'heading'\"></i>\r\n\r\n <i class=\"ec-icon {{item.icon}} ml-2\"\r\n *ngIf=\"item.icon\"></i>\r\n\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items\"></i>\r\n</ng-template>\r\n\r\n<!-- 'iconAndLabel' Item Template -->\r\n<ng-template #iconAndLabel\r\n let-item>\r\n <!-- If menuItem.icon exists and is not blank, show the icon in the menu -->\r\n <i class=\"ec-icon {{item.icon}}\"\r\n *ngIf=\"(item.icon && item.icon !== '') || preserveIconSpace\"></i>\r\n\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items\"></i>\r\n</ng-template>\r\n\r\n<ng-template #iconLabelCaption\r\n let-item>\r\n <i class=\"ec-icon {{item.icon}}\"\r\n *ngIf=\"(item.icon && item.icon !== '') || preserveIconSpace\"></i>\r\n <div *ngIf=\"item.display !== 'heading'\"\r\n class=\"label flex-grow\">\r\n <div id=\"{{item.id}}_label\"\r\n class=\"text-body-1\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</div>\r\n <div id=\"{{item.id}}_caption\"\r\n *ngIf=\"item.caption\"\r\n class=\"text-caption-1\"\r\n [class.text-truncate]=\"truncateItems\">{{item.caption}}</div>\r\n </div>\r\n <h3 *ngIf=\"item.display === 'heading'\"\r\n class=\"flex-grow text-heading-3 align-self-center\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</h3>\r\n <i class=\"ec-icon icon-angle-down rotate-270 align-self-center\"\r\n *ngIf=\"item?.items\"></i>\r\n</ng-template>", styles: ["@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(1turn)}}:host{display:block;font-size:var(--ec-menu-font-size, var(--ec-font-size-action));font-weight:400;background-color:var(--ec-menu-background-color, var(--ec-background-color))}:host.open>nav>.parent,:host.open>nav>.child{position:absolute;left:0;top:0;right:0;height:100%;transition:transform .25s ease}:host.open>nav>.parent{transform:translate(0)}:host.open>nav>.child{transform:translate(100%)}:host.open-active>nav>.parent{transform:translate(-100%)}:host.open-active>nav>.child{transform:translate(0)}:host(.bg-transparent){background-color:transparent}:host-context(.is-always-open){height:100%}:host-context(.is-always-open) .item-wrapper{padding-left:1rem;padding-right:1rem}nav{display:flex;position:relative;height:100%;overflow:hidden}.parent{display:flex;flex-direction:column;flex:auto;position:relative;max-width:100%}.parent>header{cursor:pointer}.parent>header.is-selected .item-wrapper,.parent>header.is-highlighted .item-wrapper{background-color:var(--ec-background-color-selected)}.parent>header:hover .item-wrapper{background-color:var(--ec-background-color-hover)}.parent.no-data ul{display:none}.parent.no-data .no-data-message{display:block}ul{padding:0;margin:0;list-style:none;flex:auto;height:100%;overflow-y:auto}ul li{cursor:pointer;padding:0 .25rem}ul li a{color:inherit;border-bottom:0;text-decoration:none}ul li.is-selected .item-wrapper,ul li.is-highlighted .item-wrapper{background-color:var(--ec-background-color-selected)}ul li:hover .item-wrapper{background-color:var(--ec-background-color-hover)}ul li:focus .item-wrapper{outline:none;background-color:var(--ec-color-disabled-dark);position:relative;z-index:1}ul li.is-disabled .item-wrapper{color:var(--ec-color-disabled-dark);opacity:var(--ec-form-control-opacity-disabled)}ul li.is-disabled,ul li.is-readonly{cursor:default}ul li.is-disabled .item-wrapper,ul li.is-readonly .item-wrapper{background-color:transparent;color:inherit}ul li.is-checked .icon-check{opacity:1}ul li.heading{cursor:default}ul li.heading .item-wrapper{background-color:transparent}ul li.heading:not(:first-child){margin-top:.5rem}ul li.divider{border-bottom:1px solid var(--ec-border-color);padding-bottom:.25rem;margin-bottom:.25rem}ul li.indent-1 .item-wrapper{padding-left:1.5rem}ul li.indent-2 .item-wrapper{padding-left:2.5rem}ul li.indent-3 .item-wrapper{padding-left:3.5rem}.item-wrapper{cursor:inherit;line-height:1.25rem;min-height:1.75rem;padding:.25rem .5rem;border-radius:var(--ec-border-radius);display:flex;color:inherit}.item-wrapper .label{margin-right:auto}.item-wrapper .label+.ec-icon{margin-left:.5rem}.item-wrapper .ec-icon{margin-top:calc((1.25rem - var(--ec-font-size-icon)) / 2);flex:none}.item-wrapper .ec-icon+.label{margin-left:.5rem}.item-wrapper .ec-icon-sm{margin-top:calc((1.25rem - calc(var(--ec-font-size-icon) * .75)) / 2)}.item-wrapper .icon-check{opacity:0}.no-data-message{display:none;text-align:center;padding:1rem;color:var(--ec-color-hint-dark);margin-bottom:0;font-size:var(--ec-font-size-body)}:host-context(ec-tree) ul{overflow-x:hidden}:host-context(ec-tree) li.is-selected,:host-context(ec-tree) li.is-highlighted{font-weight:700;color:var(--ec-menu-color-highlighted, inherit)}:host-context(ec-tree) li.is-selected:not(:hover),:host-context(ec-tree) li.is-highlighted:not(:hover){background-color:transparent}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: NavItemActiveDirective, selector: "[ecNavItemActive]", inputs: ["ecNavItemActive", "ecNavItemActiveExactMatch", "ecNavItemActiveQueryParams", "ecNavItemActiveUrl"], outputs: ["routerLinkActivated"] }, { kind: "component", type: MenuComponent, selector: "ec-menu", inputs: ["id", "items", "selected", "parent", "templateType", "customMenuTemplate", "title", "showNoItems", "noDataText", "enableKeyNav", "highlightedItem", "maintainSelectedItem", "truncateItems", "preserveIconSpace", "dropdownToggleButton"], outputs: ["selectedChanged", "menuClosed"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }] });
2590
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuComponent, decorators: [{
2584
+ TextboxComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: TextboxComponent, deps: [{ token: ValidationMessageService }, { token: FormGroupHelper }, { token: i2.TranslateService }], target: i0.ɵɵFactoryTarget.Component });
2585
+ TextboxComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: TextboxComponent, selector: "ec-textbox", inputs: { autocomplete: "autocomplete", type: "type", placeholder: "placeholder", maxlength: "maxlength", minlength: "minlength", rows: "rows", selectOnAutofocus: "selectOnAutofocus", upperCase: "upperCase" }, viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["textboxInput"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"control control-label-{{labelPosition}}\"\r\n [ngClass]=\"{'is-readonly': readonly}\">\r\n\r\n <label *ngIf=\"label\">\r\n <span>{{label | translate}}</span>\r\n <span *ngIf=\"validationErrors.length > 0 && formModel.touched && formModel.invalid\">&nbsp;{{validationErrors |\r\n translate}}</span>\r\n <ec-help-popover id=\"{{id}}_helpPopover\"\r\n *ngIf=\"helpPopover\"\r\n class=\"d-inline-block my-n3 mx-n1\"\r\n text=\"{{helpPopover | translate}}\"\r\n contentPosition=\"{{helpPopoverPosition}}\">\r\n </ec-help-popover>\r\n </label>\r\n\r\n <div class=\"input-wrapper control-input\">\r\n <input *ngIf=\"type !== 'multi_line'\"\r\n #textboxInput\r\n email=\"{{type === 'email' ? true : false}}\"\r\n pattern=\"{{validationPattern}}\"\r\n type=\"{{type}}\"\r\n tabindex=\"{{tabindex}}\"\r\n title=\"{{tooltip}}\"\r\n [attr.id]=\"inputId\"\r\n [attr.autocomplete]=\"autocomplete\"\r\n [attr.placeholder]=\"placeholder\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [attr.required]=\"required ? required : null\"\r\n [formControl]=\"formModel\"\r\n [ngClass]=\"{'is-empty': !formModel?.value, 'is-pending': pending, 'is-uppercase': upperCase}\"\r\n (focusout)=\"focusOutEvent()\"\r\n [attr.cdkFocusInitial]=\"autofocus || null\">\r\n\r\n <textarea *ngIf=\"type === 'multi_line'\"\r\n [attr.rows]=\"rows\"\r\n #textboxInput\r\n tabindex=\"{{tabindex}}\"\r\n [attr.id]=\"inputId\"\r\n [attr.placeholder]=\"placeholder\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [attr.required]=\"required ? required : null\"\r\n [formControl]=\"formModel\"\r\n [ngClass]=\"{'is-empty': formModel?.value === '', 'is-pending': pending}\"\r\n [attr.cdkFocusInitial]=\"autofocus || null\">\r\n </textarea>\r\n\r\n <i class=\"ec-icon icon-required\"></i>\r\n <i class=\"ec-icon icon-invalid\"></i>\r\n <i class=\"ec-icon icon-loading\"></i>\r\n </div>\r\n</div>", styles: [":host{color:var(--ec-form-control-color);font-size:var(--ec-form-control-font-size);display:block;margin-bottom:1rem;width:100%}:host :host-context(.form-condensed){margin-bottom:.5rem}:host .control{width:100%;display:flex;flex-direction:column}:host .control.control-label-bottom{flex-direction:column-reverse}:host .control.control-label-left{flex-direction:row}:host .control.control-label-left label{margin-right:.25rem}:host .control.control-label-right{flex-direction:row-reverse}:host .control.control-label-right label{margin-left:.25rem}:host .control.control-label-left,:host .control.control-label-right{align-items:center}:host .control.control-label-left label,:host .control.control-label-right label{flex:1 1;margin-top:0;margin-bottom:0}:host .control.control-label-left .control-input,:host .control.control-label-right .control-input{flex:2 2}:host .control.is-readonly input,:host .control.is-readonly select,:host .control.is-readonly textarea{border-color:var(--ec-form-control-border-color-readonly);background-color:var(--ec-form-control-background-color-readonly);background-clip:border-box;background-image:none;color:var(--ec-form-control-color-readonly);opacity:1;-webkit-user-select:none;user-select:none;pointer-events:none;overflow:hidden;white-space:nowrap}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid:focus,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid:focus{border-color:var(--ec-form-control-background-color-invalid)}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid~.icon-invalid,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid~.icon-required,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid~.icon-required{display:none}:host .control.invalid:not(.open) .textbox-group-btn-right ::ng-deep button{background-color:var(--ec-form-control-background-color-invalid)}:host .control.invalid:not(.open) .textbox-group-btn-right ::ng-deep button:not(:focus){border-color:var(--ec-form-control-border-color-invalid)}:host .textbox-group{display:flex;position:relative}:host textarea:focus,:host input:focus,:host select:focus{outline:none}:host label{color:var(--ec-form-control-label-color, var(--ec-color-secondary-dark));display:block;font-size:var(--ec-font-size-label);line-height:1;margin:calc(var(--ec-font-size-label) / 2) 0}:host input{background-color:var(--ec-form-control-background-color);border:1px solid var(--ec-form-control-border-color);border-radius:var(--ec-border-radius);background-image:none;background-clip:padding-box;width:100%;line-height:1.25rem;padding:.3125rem .5rem;height:2rem}:host input::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host input::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host input::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host input:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host input:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host input~.icon-required,:host input~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host input:required.is-empty{background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host input:required.is-empty~.icon-required{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host input.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host input.ng-invalid.ng-touched~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.ng-invalid.ng-touched~.icon-required{display:none}:host input.is-pending.ng-valid,:host input.is-pending.ng-invalid,:host input.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host input.is-pending.ng-valid~.icon-loading,:host input.is-pending.ng-invalid~.icon-loading,:host input.is-pending.ng-pending~.icon-loading{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.is-pending.ng-valid~.icon-required,:host input.is-pending.ng-valid~.icon-invalid,:host input.is-pending.ng-invalid~.icon-required,:host input.is-pending.ng-invalid~.icon-invalid,:host input.is-pending.ng-pending~.icon-required,:host input.is-pending.ng-pending~.icon-invalid{display:none}:host input:focus,:host input:focus.is-empty{border-color:var(--ec-form-control-border-color-focus);box-shadow:var(--ec-form-control-box-shadow-focus);position:relative;z-index:1}:host input:disabled{background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled);color:var(--ec-form-control-color-disabled);opacity:var(--ec-form-control-opacity-disabled)}:host input:disabled:required,:host input:disabled:required.is-empty{background-image:none;padding-left:.5rem;background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled)}:host input:disabled:required+.icon-required,:host input:disabled:required.is-empty+.icon-required{display:none}:host input.is-uppercase:not(.is-empty){text-transform:uppercase}:host textarea{background-color:var(--ec-form-control-background-color);border:1px solid var(--ec-form-control-border-color);border-radius:var(--ec-border-radius);background-image:none;background-clip:padding-box;width:100%;line-height:1.25rem;padding:.3125rem .5rem;height:auto;resize:none;display:block}:host textarea::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host textarea::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host textarea::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host textarea:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host textarea:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host textarea~.icon-required,:host textarea~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host textarea:required.is-empty{background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host textarea:required.is-empty~.icon-required{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host textarea.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host textarea.ng-invalid.ng-touched~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.ng-invalid.ng-touched~.icon-required{display:none}:host textarea.is-pending.ng-valid,:host textarea.is-pending.ng-invalid,:host textarea.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem}:host textarea.is-pending.ng-valid~.icon-loading,:host textarea.is-pending.ng-invalid~.icon-loading,:host textarea.is-pending.ng-pending~.icon-loading{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.is-pending.ng-valid~.icon-required,:host textarea.is-pending.ng-valid~.icon-invalid,:host textarea.is-pending.ng-invalid~.icon-required,:host textarea.is-pending.ng-invalid~.icon-invalid,:host textarea.is-pending.ng-pending~.icon-required,:host textarea.is-pending.ng-pending~.icon-invalid{display:none}:host textarea:focus,:host textarea:focus.is-empty{border-color:var(--ec-form-control-border-color-focus);box-shadow:var(--ec-form-control-box-shadow-focus);position:relative;z-index:1}:host textarea:disabled{background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled);color:var(--ec-form-control-color-disabled);opacity:var(--ec-form-control-opacity-disabled)}:host textarea:disabled:required,:host textarea:disabled:required.is-empty{background-image:none;padding-left:.5rem;background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled)}:host textarea:disabled:required+.icon-required,:host textarea:disabled:required.is-empty+.icon-required{display:none}:host textarea.is-uppercase:not(.is-empty){text-transform:uppercase}.input-wrapper{position:relative}.input-wrapper>.ec-icon{display:none}:host(.textbox-group-input:not(:last-child)){flex:1 1 0%;width:1px}:host(.textbox-group-input:not(:last-child)) .control{margin-bottom:0}:host(.textbox-group-input:not(:last-child)) .control.is-readonly input{border-right-width:1px}:host(.textbox-group-input:not(:last-child)) input{border-top-right-radius:0;border-bottom-right-radius:0;border-right-width:0}:host(.textbox-group-input:not(:last-child)) input:focus{position:relative;z-index:1;border-right-width:1px}:host(.text-truncate) input{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host(.is-monospace) input,:host(.is-monospace) textarea,:host-context(.is-monospace) input,:host-context(.is-monospace) textarea{font-family:var(--ec-font-family-monospace)}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i4.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i4.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i4.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: HelpPopoverComponent, selector: "ec-help-popover", inputs: ["id", "text", "contentPosition", "maxWidth"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }] });
2586
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: TextboxComponent, decorators: [{
2591
2587
  type: Component,
2592
- args: [{ selector: 'ec-menu', template: "<nav>\r\n <div class=\"parent\"\r\n [class.no-data]=\"showNoItems && (!items || items.length === 0)\">\r\n <header id=\"{{id}}_header\"\r\n class=\"text-heading-3 p-1\"\r\n [class.is-selected]=\"highlightedItemIndex === -1\"\r\n *ngIf=\"parent\"\r\n (click)=\"back($event)\">\r\n <div class=\"item-wrapper\">\r\n <i class=\"ec-icon icon-angle-down rotate-90 flex-shrink\"></i>\r\n <span class=\"label text-truncate flex-grow\">{{parent?.label}}</span>\r\n </div>\r\n </header>\r\n\r\n <ul id=\"{{id}}_list\"\r\n class=\"py-1\">\r\n <li *ngFor=\"let item of items; index as i\"\r\n id=\"{{item.id || id + '_item' + i}}\"\r\n class=\"{{item.display || 'item'}} {{item.classList}}\"\r\n [attr.disabled]=\"item.disabled\"\r\n [hidden]=\"item.hidden\"\r\n ecNavItemActive=\"is-selected\"\r\n [ecNavItemActiveQueryParams]=\"item.queryParams\"\r\n [ecNavItemActiveUrl]=\"item.url\"\r\n [ecNavItemActiveExactMatch]='item.isActiveExactMatch'\r\n (routerLinkActivated)=\"selectItem($event, item)\"\r\n [ngClass]=\"{'is-highlighted':(selected === item && item?.display !== 'heading') || highlightedItem === item, 'is-link': item.url, 'is-disabled': item.disabled, 'is-readonly': item.readonly, 'is-checked': item.checked, 'text-heading-3': item?.display === 'heading'}\"\r\n (click)=\"selectItem($event, item)\">\r\n\r\n <a *ngIf=\"item.url && !item.externalLink\"\r\n id=\"{{item.id}}_link\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\"\r\n [routerLink]=\"item.url\"\r\n [queryParams]=\"item.queryParams || null\"\r\n target=\"{{item.target || '_self'}}\">\r\n\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </a>\r\n\r\n <a *ngIf=\"item.url && item.externalLink\"\r\n id=\"{{item.id}}_link\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\"\r\n href=\"{{item.url}}\"\r\n target=\"{{item.target || '_self'}}\">\r\n\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </a>\r\n\r\n <div *ngIf=\"!item.url\"\r\n title=\"{{truncateItems ? item.label : ''}}\"\r\n class=\"item-wrapper\">\r\n <ng-container *ngTemplateOutlet=\"internalizedTemplate; context: {$implicit: item}\"></ng-container>\r\n </div>\r\n </li>\r\n </ul>\r\n\r\n <p class=\"no-data-message\">{{noDataText | translate}}</p>\r\n </div>\r\n\r\n <!-- Child menu (Rendered to the right) -->\r\n <ec-menu *ngIf=\"selected?.items\"\r\n id=\"{{id}}_child\"\r\n class=\"child\"\r\n [parent]=\"selected\"\r\n [items]=\"selected?.items\"\r\n [showNoItems]=\"true\"\r\n [templateType]=\"templateType\"\r\n [enableKeyNav]=\"true\"\r\n [truncateItems]=\"truncateItems\"\r\n (selectedChanged)=\"onSelection($event)\"\r\n (menuClosed)=\"toggleChildMenu(false)\">\r\n </ec-menu>\r\n</nav>\r\n\r\n<!-- 'label' Item Template -->\r\n<ng-template #label\r\n let-item>\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items\"></i>\r\n</ng-template>\r\n\r\n<!-- 'checkAndLabel' Item Template -->\r\n<ng-template #checkAndLabel\r\n let-item>\r\n\r\n <i class=\"ec-icon icon-check ec-icon-sm\"\r\n *ngIf=\"item.display !== 'heading'\"></i>\r\n\r\n <i class=\"ec-icon {{item.icon}} ml-2\"\r\n *ngIf=\"item.icon\"></i>\r\n\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items\"></i>\r\n</ng-template>\r\n\r\n<!-- 'iconAndLabel' Item Template -->\r\n<ng-template #iconAndLabel\r\n let-item>\r\n <!-- If menuItem.icon exists and is not blank, show the icon in the menu -->\r\n <i class=\"ec-icon {{item.icon}}\"\r\n *ngIf=\"(item.icon && item.icon !== '') || preserveIconSpace\"></i>\r\n\r\n <span id=\"{{item.id}}_label\"\r\n class=\"label\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</span>\r\n\r\n <i class=\"ec-icon icon-angle-down rotate-270\"\r\n *ngIf=\"item?.items\"></i>\r\n</ng-template>\r\n\r\n<ng-template #iconLabelCaption\r\n let-item>\r\n <i class=\"ec-icon {{item.icon}}\"\r\n *ngIf=\"(item.icon && item.icon !== '') || preserveIconSpace\"></i>\r\n <div *ngIf=\"item.display !== 'heading'\"\r\n class=\"label flex-grow\">\r\n <div id=\"{{item.id}}_label\"\r\n class=\"text-body-1\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</div>\r\n <div id=\"{{item.id}}_caption\"\r\n *ngIf=\"item.caption\"\r\n class=\"text-caption-1\"\r\n [class.text-truncate]=\"truncateItems\">{{item.caption}}</div>\r\n </div>\r\n <h3 *ngIf=\"item.display === 'heading'\"\r\n class=\"flex-grow text-heading-3 align-self-center\"\r\n [class.text-truncate]=\"truncateItems\">{{item.label}}</h3>\r\n <i class=\"ec-icon icon-angle-down rotate-270 align-self-center\"\r\n *ngIf=\"item?.items\"></i>\r\n</ng-template>", styles: ["@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(1turn)}}:host{display:block;font-size:var(--ec-menu-font-size, var(--ec-font-size-action));font-weight:400;background-color:var(--ec-menu-background-color, var(--ec-background-color))}:host.open>nav>.parent,:host.open>nav>.child{position:absolute;left:0;top:0;right:0;height:100%;transition:transform .25s ease}:host.open>nav>.parent{transform:translate(0)}:host.open>nav>.child{transform:translate(100%)}:host.open-active>nav>.parent{transform:translate(-100%)}:host.open-active>nav>.child{transform:translate(0)}:host(.bg-transparent){background-color:transparent}:host-context(.is-always-open){height:100%}:host-context(.is-always-open) .item-wrapper{padding-left:1rem;padding-right:1rem}nav{display:flex;position:relative;height:100%;overflow:hidden}.parent{display:flex;flex-direction:column;flex:auto;position:relative;max-width:100%}.parent>header{cursor:pointer}.parent>header.is-selected .item-wrapper,.parent>header.is-highlighted .item-wrapper{background-color:var(--ec-background-color-selected)}.parent>header:hover .item-wrapper{background-color:var(--ec-background-color-hover)}.parent.no-data ul{display:none}.parent.no-data .no-data-message{display:block}ul{padding:0;margin:0;list-style:none;flex:auto;height:100%;overflow-y:auto}ul li{cursor:pointer;padding:0 .25rem}ul li a{color:inherit;border-bottom:0;text-decoration:none}ul li.is-selected .item-wrapper,ul li.is-highlighted .item-wrapper{background-color:var(--ec-background-color-selected)}ul li:hover .item-wrapper{background-color:var(--ec-background-color-hover)}ul li:focus .item-wrapper{outline:none;background-color:var(--ec-color-disabled-dark);position:relative;z-index:1}ul li.is-disabled .item-wrapper{color:var(--ec-color-disabled-dark);opacity:var(--ec-form-control-opacity-disabled)}ul li.is-disabled,ul li.is-readonly{cursor:default}ul li.is-disabled .item-wrapper,ul li.is-readonly .item-wrapper{background-color:transparent;color:inherit}ul li.is-checked .icon-check{opacity:1}ul li.heading{cursor:default}ul li.heading .item-wrapper{background-color:transparent}ul li.heading:not(:first-child){margin-top:.5rem}ul li.divider{border-bottom:1px solid var(--ec-border-color);padding-bottom:.25rem;margin-bottom:.25rem}ul li.indent-1 .item-wrapper{padding-left:1.5rem}ul li.indent-2 .item-wrapper{padding-left:2.5rem}ul li.indent-3 .item-wrapper{padding-left:3.5rem}.item-wrapper{cursor:inherit;line-height:1.25rem;min-height:1.75rem;padding:.25rem .5rem;border-radius:var(--ec-border-radius);display:flex;color:inherit}.item-wrapper .label{margin-right:auto}.item-wrapper .label+.ec-icon{margin-left:.5rem}.item-wrapper .ec-icon{margin-top:calc((1.25rem - var(--ec-font-size-icon)) / 2);flex:none}.item-wrapper .ec-icon+.label{margin-left:.5rem}.item-wrapper .ec-icon-sm{margin-top:calc((1.25rem - calc(var(--ec-font-size-icon) * .75)) / 2)}.item-wrapper .icon-check{opacity:0}.no-data-message{display:none;text-align:center;padding:1rem;color:var(--ec-color-hint-dark);margin-bottom:0;font-size:var(--ec-font-size-body)}:host-context(ec-tree) ul{overflow-x:hidden}:host-context(ec-tree) li.is-selected,:host-context(ec-tree) li.is-highlighted{font-weight:700;color:var(--ec-menu-color-highlighted, inherit)}:host-context(ec-tree) li.is-selected:not(:hover),:host-context(ec-tree) li.is-highlighted:not(:hover){background-color:transparent}\n"] }]
2593
- }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: WindowService }, { type: ScrollService }]; }, propDecorators: { id: [{
2594
- type: Input
2595
- }], attrId: [{
2596
- type: HostBinding,
2597
- args: ['attr.id']
2598
- }], items: [{
2588
+ args: [{ selector: 'ec-textbox', template: "<div class=\"control control-label-{{labelPosition}}\"\r\n [ngClass]=\"{'is-readonly': readonly}\">\r\n\r\n <label *ngIf=\"label\">\r\n <span>{{label | translate}}</span>\r\n <span *ngIf=\"validationErrors.length > 0 && formModel.touched && formModel.invalid\">&nbsp;{{validationErrors |\r\n translate}}</span>\r\n <ec-help-popover id=\"{{id}}_helpPopover\"\r\n *ngIf=\"helpPopover\"\r\n class=\"d-inline-block my-n3 mx-n1\"\r\n text=\"{{helpPopover | translate}}\"\r\n contentPosition=\"{{helpPopoverPosition}}\">\r\n </ec-help-popover>\r\n </label>\r\n\r\n <div class=\"input-wrapper control-input\">\r\n <input *ngIf=\"type !== 'multi_line'\"\r\n #textboxInput\r\n email=\"{{type === 'email' ? true : false}}\"\r\n pattern=\"{{validationPattern}}\"\r\n type=\"{{type}}\"\r\n tabindex=\"{{tabindex}}\"\r\n title=\"{{tooltip}}\"\r\n [attr.id]=\"inputId\"\r\n [attr.autocomplete]=\"autocomplete\"\r\n [attr.placeholder]=\"placeholder\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [attr.required]=\"required ? required : null\"\r\n [formControl]=\"formModel\"\r\n [ngClass]=\"{'is-empty': !formModel?.value, 'is-pending': pending, 'is-uppercase': upperCase}\"\r\n (focusout)=\"focusOutEvent()\"\r\n [attr.cdkFocusInitial]=\"autofocus || null\">\r\n\r\n <textarea *ngIf=\"type === 'multi_line'\"\r\n [attr.rows]=\"rows\"\r\n #textboxInput\r\n tabindex=\"{{tabindex}}\"\r\n [attr.id]=\"inputId\"\r\n [attr.placeholder]=\"placeholder\"\r\n [attr.maxlength]=\"maxlength\"\r\n [attr.minlength]=\"minlength\"\r\n [attr.required]=\"required ? required : null\"\r\n [formControl]=\"formModel\"\r\n [ngClass]=\"{'is-empty': formModel?.value === '', 'is-pending': pending}\"\r\n [attr.cdkFocusInitial]=\"autofocus || null\">\r\n </textarea>\r\n\r\n <i class=\"ec-icon icon-required\"></i>\r\n <i class=\"ec-icon icon-invalid\"></i>\r\n <i class=\"ec-icon icon-loading\"></i>\r\n </div>\r\n</div>", styles: [":host{color:var(--ec-form-control-color);font-size:var(--ec-form-control-font-size);display:block;margin-bottom:1rem;width:100%}:host :host-context(.form-condensed){margin-bottom:.5rem}:host .control{width:100%;display:flex;flex-direction:column}:host .control.control-label-bottom{flex-direction:column-reverse}:host .control.control-label-left{flex-direction:row}:host .control.control-label-left label{margin-right:.25rem}:host .control.control-label-right{flex-direction:row-reverse}:host .control.control-label-right label{margin-left:.25rem}:host .control.control-label-left,:host .control.control-label-right{align-items:center}:host .control.control-label-left label,:host .control.control-label-right label{flex:1 1;margin-top:0;margin-bottom:0}:host .control.control-label-left .control-input,:host .control.control-label-right .control-input{flex:2 2}:host .control.is-readonly input,:host .control.is-readonly select,:host .control.is-readonly textarea{border-color:var(--ec-form-control-border-color-readonly);background-color:var(--ec-form-control-background-color-readonly);background-clip:border-box;background-image:none;color:var(--ec-form-control-color-readonly);opacity:1;-webkit-user-select:none;user-select:none;pointer-events:none;overflow:hidden;white-space:nowrap}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid:focus,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid:focus{border-color:var(--ec-form-control-background-color-invalid)}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid~.icon-invalid,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-invalid~.icon-required,:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid~.icon-required{display:none}:host .control.invalid:not(.open) .textbox-group-btn-right ::ng-deep button{background-color:var(--ec-form-control-background-color-invalid)}:host .control.invalid:not(.open) .textbox-group-btn-right ::ng-deep button:not(:focus){border-color:var(--ec-form-control-border-color-invalid)}:host .textbox-group{display:flex;position:relative}:host textarea:focus,:host input:focus,:host select:focus{outline:none}:host label{color:var(--ec-form-control-label-color, var(--ec-color-secondary-dark));display:block;font-size:var(--ec-font-size-label);line-height:1;margin:calc(var(--ec-font-size-label) / 2) 0}:host input{background-color:var(--ec-form-control-background-color);border:1px solid var(--ec-form-control-border-color);border-radius:var(--ec-border-radius);background-image:none;background-clip:padding-box;width:100%;line-height:1.25rem;padding:.3125rem .5rem;height:2rem}:host input::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host input::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host input::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host input:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host input:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host input~.icon-required,:host input~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host input:required.is-empty{background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host input:required.is-empty~.icon-required{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host input.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host input.ng-invalid.ng-touched~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.ng-invalid.ng-touched~.icon-required{display:none}:host input.is-pending.ng-valid,:host input.is-pending.ng-invalid,:host input.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host input.is-pending.ng-valid~.icon-loading,:host input.is-pending.ng-invalid~.icon-loading,:host input.is-pending.ng-pending~.icon-loading{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host input.is-pending.ng-valid~.icon-required,:host input.is-pending.ng-valid~.icon-invalid,:host input.is-pending.ng-invalid~.icon-required,:host input.is-pending.ng-invalid~.icon-invalid,:host input.is-pending.ng-pending~.icon-required,:host input.is-pending.ng-pending~.icon-invalid{display:none}:host input:focus,:host input:focus.is-empty{border-color:var(--ec-form-control-border-color-focus);box-shadow:var(--ec-form-control-box-shadow-focus);position:relative;z-index:1}:host input:disabled{background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled);color:var(--ec-form-control-color-disabled);opacity:var(--ec-form-control-opacity-disabled)}:host input:disabled:required,:host input:disabled:required.is-empty{background-image:none;padding-left:.5rem;background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled)}:host input:disabled:required+.icon-required,:host input:disabled:required.is-empty+.icon-required{display:none}:host input.is-uppercase:not(.is-empty){text-transform:uppercase}:host textarea{background-color:var(--ec-form-control-background-color);border:1px solid var(--ec-form-control-border-color);border-radius:var(--ec-border-radius);background-image:none;background-clip:padding-box;width:100%;line-height:1.25rem;padding:.3125rem .5rem;height:auto;resize:none;display:block}:host textarea::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host textarea::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host textarea::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host textarea:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host textarea:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host textarea~.icon-required,:host textarea~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host textarea:required.is-empty{background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host textarea:required.is-empty~.icon-required{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid);background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem;background-image:none}:host textarea.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host textarea.ng-invalid.ng-touched~.icon-invalid{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.ng-invalid.ng-touched~.icon-required{display:none}:host textarea.is-pending.ng-valid,:host textarea.is-pending.ng-invalid,:host textarea.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem .5rem;background-size:1rem,1rem;padding-left:1.75rem}:host textarea.is-pending.ng-valid~.icon-loading,:host textarea.is-pending.ng-invalid~.icon-loading,:host textarea.is-pending.ng-pending~.icon-loading{display:inline-flex;position:absolute;left:.5rem;top:.5rem;z-index:1}:host textarea.is-pending.ng-valid~.icon-required,:host textarea.is-pending.ng-valid~.icon-invalid,:host textarea.is-pending.ng-invalid~.icon-required,:host textarea.is-pending.ng-invalid~.icon-invalid,:host textarea.is-pending.ng-pending~.icon-required,:host textarea.is-pending.ng-pending~.icon-invalid{display:none}:host textarea:focus,:host textarea:focus.is-empty{border-color:var(--ec-form-control-border-color-focus);box-shadow:var(--ec-form-control-box-shadow-focus);position:relative;z-index:1}:host textarea:disabled{background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled);color:var(--ec-form-control-color-disabled);opacity:var(--ec-form-control-opacity-disabled)}:host textarea:disabled:required,:host textarea:disabled:required.is-empty{background-image:none;padding-left:.5rem;background-color:var(--ec-form-control-background-color-disabled);border-color:var(--ec-form-control-border-color-disabled)}:host textarea:disabled:required+.icon-required,:host textarea:disabled:required.is-empty+.icon-required{display:none}:host textarea.is-uppercase:not(.is-empty){text-transform:uppercase}.input-wrapper{position:relative}.input-wrapper>.ec-icon{display:none}:host(.textbox-group-input:not(:last-child)){flex:1 1 0%;width:1px}:host(.textbox-group-input:not(:last-child)) .control{margin-bottom:0}:host(.textbox-group-input:not(:last-child)) .control.is-readonly input{border-right-width:1px}:host(.textbox-group-input:not(:last-child)) input{border-top-right-radius:0;border-bottom-right-radius:0;border-right-width:0}:host(.textbox-group-input:not(:last-child)) input:focus{position:relative;z-index:1;border-right-width:1px}:host(.text-truncate) input{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host(.is-monospace) input,:host(.is-monospace) textarea,:host-context(.is-monospace) input,:host-context(.is-monospace) textarea{font-family:var(--ec-font-family-monospace)}\n"] }]
2589
+ }], ctorParameters: function () { return [{ type: ValidationMessageService }, { type: FormGroupHelper }, { type: i2.TranslateService }]; }, propDecorators: { autocomplete: [{
2599
2590
  type: Input
2600
- }], selected: [{
2591
+ }], type: [{
2601
2592
  type: Input
2602
- }], parent: [{
2593
+ }], placeholder: [{
2603
2594
  type: Input
2604
- }], templateType: [{
2595
+ }], maxlength: [{
2605
2596
  type: Input
2606
- }], customMenuTemplate: [{
2597
+ }], minlength: [{
2607
2598
  type: Input
2608
- }], title: [{
2599
+ }], rows: [{
2609
2600
  type: Input
2610
- }], showNoItems: [{
2601
+ }], selectOnAutofocus: [{
2611
2602
  type: Input
2612
- }], noDataText: [{
2603
+ }], upperCase: [{
2613
2604
  type: Input
2614
- }], enableKeyNav: [{
2605
+ }], inputElement: [{
2606
+ type: ViewChild,
2607
+ args: ['textboxInput']
2608
+ }] } });
2609
+
2610
+ /** Exposes the markup and styles that represent the spinner. No inputs or outputs defined because it is just a visual component*/
2611
+ class SpinnerComponent {
2612
+ }
2613
+ SpinnerComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: SpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2614
+ SpinnerComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: SpinnerComponent, selector: "ec-spinner", ngImport: i0, template: "<div class=\"spinner\">\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n</div>", styles: ["@keyframes sk-bouncedelay{0%,80%,to{opacity:0}40%{opacity:1}}.spinner{display:flex}.spinner-dot{width:.75rem;height:.75rem;background-color:var(--ec-color-interactive);animation:sk-bouncedelay 1.7s infinite ease-in-out both;margin-right:.25rem}.spinner-dot:nth-child(1){animation-delay:-.6s}.spinner-dot:nth-child(2){animation-delay:-.4s}.spinner-dot:nth-child(3){animation-delay:-.2s}:host(.spinner-small) .spinner-dot{width:.5rem;height:.5rem;background-color:var(--ec-color-interactive);animation:sk-bouncedelay 1.7s infinite ease-in-out both;margin-right:.1666666667rem}:host(.spinner-small) .spinner-dot:nth-child(1){animation-delay:-.6s}:host(.spinner-small) .spinner-dot:nth-child(2){animation-delay:-.4s}:host(.spinner-small) .spinner-dot:nth-child(3){animation-delay:-.2s}\n"] });
2615
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: SpinnerComponent, decorators: [{
2616
+ type: Component,
2617
+ args: [{ selector: 'ec-spinner', template: "<div class=\"spinner\">\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n <span class=\"spinner-dot\"></span>\r\n</div>", styles: ["@keyframes sk-bouncedelay{0%,80%,to{opacity:0}40%{opacity:1}}.spinner{display:flex}.spinner-dot{width:.75rem;height:.75rem;background-color:var(--ec-color-interactive);animation:sk-bouncedelay 1.7s infinite ease-in-out both;margin-right:.25rem}.spinner-dot:nth-child(1){animation-delay:-.6s}.spinner-dot:nth-child(2){animation-delay:-.4s}.spinner-dot:nth-child(3){animation-delay:-.2s}:host(.spinner-small) .spinner-dot{width:.5rem;height:.5rem;background-color:var(--ec-color-interactive);animation:sk-bouncedelay 1.7s infinite ease-in-out both;margin-right:.1666666667rem}:host(.spinner-small) .spinner-dot:nth-child(1){animation-delay:-.6s}:host(.spinner-small) .spinner-dot:nth-child(2){animation-delay:-.4s}:host(.spinner-small) .spinner-dot:nth-child(3){animation-delay:-.2s}\n"] }]
2618
+ }] });
2619
+
2620
+ class Overlay {
2621
+ constructor(status, message) {
2622
+ this.status = 'hasData';
2623
+ this.message = '';
2624
+ this.setStatus(status, message);
2625
+ }
2626
+ setStatus(status, message, action, noDataTemplate, overlayClassList) {
2627
+ this.status = status;
2628
+ this.message = message || '';
2629
+ this.action = action || undefined;
2630
+ this.noDataTemplate = noDataTemplate || undefined;
2631
+ this.overlayClassList = overlayClassList || '';
2632
+ }
2633
+ }
2634
+ /**
2635
+ * Wraps content in order to show pending, error, and no data states with an optional message/noDataTemplate
2636
+ */
2637
+ class ViewOverlayComponent {
2638
+ constructor() {
2639
+ this.status = 'hasData';
2640
+ }
2641
+ setStatus(status, message, action, noDataTemplate) {
2642
+ this.status = status;
2643
+ this.message = message || '';
2644
+ this.action = action || undefined;
2645
+ this.noDataTemplate = noDataTemplate || undefined;
2646
+ }
2647
+ actionClicked(event) {
2648
+ if (this.action && this.action.onClick) {
2649
+ this.action.onClick(event);
2650
+ }
2651
+ }
2652
+ }
2653
+ ViewOverlayComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: ViewOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2654
+ ViewOverlayComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: ViewOverlayComponent, selector: "[ecOverlay]", inputs: { status: "status", message: "message", action: "action", noDataTemplate: "noDataTemplate", displayAsMask: "displayAsMask", overlayClassList: "overlayClassList" }, ngImport: i0, template: "<!-- Transcluded Content -->\r\n<ng-content *ngIf=\"displayAsMask || (!displayAsMask && status === 'hasData')\"></ng-content>\r\n<!--Used by GI tests to know the overlay status whether we use ngIf or mask version. No visual impact-->\r\n<span [hidden]=\"true\"\r\n\t class=\"overlay-status-{{status}}\"></span>\r\n<!-- Overlay goes last so it is rendered on top of preceding content due to source order -->\r\n<div *ngIf=\"status !== 'hasData'\"\r\n\t class=\"overlay flex-grow {{overlayClassList}}\"\r\n\t [ngClass]=\"{'not-mask': !displayAsMask,\r\n\t\t\t\t'overlay-error': status === 'error',\r\n\t\t\t\t'overlay-nodata': status === 'noData',\r\n\t\t\t\t'overlay-pending': status === 'pending'}\">\r\n\r\n\t<!--Pending Spinner-->\r\n\t<ec-spinner [hidden]=\"status !== 'pending'\"></ec-spinner>\r\n\r\n\t<ng-template [ngIf]=\"status === 'noData' && noDataTemplate\">\r\n\t\t<ng-container *ngTemplateOutlet=\"noDataTemplate\"></ng-container>\r\n\t</ng-template>\r\n\r\n\t<ng-container *ngIf=\"(status === 'noData' && !noDataTemplate) || status !== 'noData'\">\r\n\t\t<!--Status Message-->\r\n\t\t<div id=\"statusMessage\"\r\n\t\t\t class=\"message\"\r\n\t\t\t *ngIf=\"message\"\r\n\t\t\t [ngClass]=\"{'error': status === 'error', 'mt-1': status === 'pending'}\"\r\n\t\t\t [innerHtml]=\"message | translate\">\r\n\t\t</div>\r\n\r\n\t\t<!-- Action -->\r\n\t\t<ec-button type=\"common\"\r\n\t\t\t\t class=\"mt-3\"\r\n\t\t\t\t *ngIf=\"action?.onClick\"\r\n\t\t\t\t [icon]=\"action?.icon\"\r\n\t\t\t\t (clicked)=\"actionClicked($event)\"\r\n\t\t\t\t [label]=\"action?.label\"\r\n\t\t\t\t [hidden]=\"status === 'pending'\">\r\n\t\t</ec-button>\r\n\t</ng-container>\r\n\r\n</div>", styles: [":host{position:relative}:host(.bg-body)>.overlay{background-color:var(--ec-background-color-body)}:host(.bg-body).is-translucent>.overlay{background-color:var(--ec-background-color-overlay)}:host(.bg-content)>.overlay{background-color:var(--ec-background-color)}:host(.bg-content).is-translucent>.overlay{background-color:var(--ec-background-color-overlay)}.overlay{align-items:center;background-color:var(--ec-overlay-background-color, var(--ec-background-color));display:flex;flex-direction:column;justify-content:center;padding:3rem 4rem;z-index:var(--ec-z-index-overlay);position:absolute;inset:0}.overlay.not-mask{position:relative;min-height:100%}.message{color:var(--ec-color-secondary-dark);font-size:var(--ec-font-size-title)}.message.error{color:var(--ec-color-danger);font-size:var(--ec-font-size-title)}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ButtonComponent, selector: "ec-button", inputs: ["id", "disabled", "icon", "label", "badge", "tabindex", "type", "pending", "pendingIcon", "customTemplate", "isSubmit", "autofocus"], outputs: ["clicked"] }, { kind: "component", type: SpinnerComponent, selector: "ec-spinner" }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }] });
2655
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: ViewOverlayComponent, decorators: [{
2656
+ type: Component,
2657
+ args: [{ selector: '[ecOverlay]', template: "<!-- Transcluded Content -->\r\n<ng-content *ngIf=\"displayAsMask || (!displayAsMask && status === 'hasData')\"></ng-content>\r\n<!--Used by GI tests to know the overlay status whether we use ngIf or mask version. No visual impact-->\r\n<span [hidden]=\"true\"\r\n\t class=\"overlay-status-{{status}}\"></span>\r\n<!-- Overlay goes last so it is rendered on top of preceding content due to source order -->\r\n<div *ngIf=\"status !== 'hasData'\"\r\n\t class=\"overlay flex-grow {{overlayClassList}}\"\r\n\t [ngClass]=\"{'not-mask': !displayAsMask,\r\n\t\t\t\t'overlay-error': status === 'error',\r\n\t\t\t\t'overlay-nodata': status === 'noData',\r\n\t\t\t\t'overlay-pending': status === 'pending'}\">\r\n\r\n\t<!--Pending Spinner-->\r\n\t<ec-spinner [hidden]=\"status !== 'pending'\"></ec-spinner>\r\n\r\n\t<ng-template [ngIf]=\"status === 'noData' && noDataTemplate\">\r\n\t\t<ng-container *ngTemplateOutlet=\"noDataTemplate\"></ng-container>\r\n\t</ng-template>\r\n\r\n\t<ng-container *ngIf=\"(status === 'noData' && !noDataTemplate) || status !== 'noData'\">\r\n\t\t<!--Status Message-->\r\n\t\t<div id=\"statusMessage\"\r\n\t\t\t class=\"message\"\r\n\t\t\t *ngIf=\"message\"\r\n\t\t\t [ngClass]=\"{'error': status === 'error', 'mt-1': status === 'pending'}\"\r\n\t\t\t [innerHtml]=\"message | translate\">\r\n\t\t</div>\r\n\r\n\t\t<!-- Action -->\r\n\t\t<ec-button type=\"common\"\r\n\t\t\t\t class=\"mt-3\"\r\n\t\t\t\t *ngIf=\"action?.onClick\"\r\n\t\t\t\t [icon]=\"action?.icon\"\r\n\t\t\t\t (clicked)=\"actionClicked($event)\"\r\n\t\t\t\t [label]=\"action?.label\"\r\n\t\t\t\t [hidden]=\"status === 'pending'\">\r\n\t\t</ec-button>\r\n\t</ng-container>\r\n\r\n</div>", styles: [":host{position:relative}:host(.bg-body)>.overlay{background-color:var(--ec-background-color-body)}:host(.bg-body).is-translucent>.overlay{background-color:var(--ec-background-color-overlay)}:host(.bg-content)>.overlay{background-color:var(--ec-background-color)}:host(.bg-content).is-translucent>.overlay{background-color:var(--ec-background-color-overlay)}.overlay{align-items:center;background-color:var(--ec-overlay-background-color, var(--ec-background-color));display:flex;flex-direction:column;justify-content:center;padding:3rem 4rem;z-index:var(--ec-z-index-overlay);position:absolute;inset:0}.overlay.not-mask{position:relative;min-height:100%}.message{color:var(--ec-color-secondary-dark);font-size:var(--ec-font-size-title)}.message.error{color:var(--ec-color-danger);font-size:var(--ec-font-size-title)}\n"] }]
2658
+ }], propDecorators: { status: [{
2615
2659
  type: Input
2616
- }], highlightedItem: [{
2660
+ }], message: [{
2617
2661
  type: Input
2618
- }], maintainSelectedItem: [{
2662
+ }], action: [{
2619
2663
  type: Input
2620
- }], truncateItems: [{
2664
+ }], noDataTemplate: [{
2621
2665
  type: Input
2622
- }], preserveIconSpace: [{
2666
+ }], displayAsMask: [{
2623
2667
  type: Input
2624
- }], dropdownToggleButton: [{
2668
+ }], overlayClassList: [{
2625
2669
  type: Input
2626
- }], selectedChanged: [{
2627
- type: Output
2628
- }], menuClosed: [{
2629
- type: Output
2630
- }], labelTemplate: [{
2631
- type: ViewChild,
2632
- args: ['label', { static: true }]
2633
- }], iconAndLabelTemplate: [{
2634
- type: ViewChild,
2635
- args: ['iconAndLabel', { static: true }]
2636
- }], checkAndLabelTemplate: [{
2637
- type: ViewChild,
2638
- args: ['checkAndLabel', { static: true }]
2639
- }], iconLabelCaptionTemplate: [{
2640
- type: ViewChild,
2641
- args: ['iconLabelCaption', { static: true }]
2642
2670
  }] } });
2643
2671
 
2644
2672
  /**
@@ -2794,6 +2822,11 @@ class ComboboxComponent extends FormControlBase {
2794
2822
  * Number of filtered options to display in the footer. Excludes headings.
2795
2823
  */
2796
2824
  this.filteredOptionCount = 0;
2825
+ /**
2826
+ * Flat list of selectable items in the combobox.
2827
+ * Does not include headings or divider-section items.
2828
+ */
2829
+ this.selectableItems = [];
2797
2830
  /**
2798
2831
  * Index of the currently-selected options
2799
2832
  */
@@ -2930,13 +2963,14 @@ class ComboboxComponent extends FormControlBase {
2930
2963
  */
2931
2964
  resetOptions(options) {
2932
2965
  this.filteredOptions = options || this.options;
2966
+ this.selectableItems = MenuComponent.getSelectableItems(this.filteredOptions);
2933
2967
  // do not count headings
2934
- this.filteredOptionCount = this.filteredOptions?.filter(o => o.display !== 'heading').length || 0;
2968
+ this.filteredOptionCount = this.filteredOptions?.filter(o => o.display !== 'heading' && o.display !== 'divided-section').length || 0;
2935
2969
  //if they have no search term, don't try to select anything so they can clear the box out by hitting enter
2936
2970
  //if they have a search term and the options changed, select an option from the menu so they will get it automatically if they hit enter
2937
2971
  if (this.textboxFormModel.value !== '') {
2938
- this.selectedItemIndex = this.findDefaultSelectionIndex(this.filteredOptions, this.textboxFormModel.value);
2939
- this.selectedItem = this.filteredOptions[this.selectedItemIndex] || null;
2972
+ this.selectedItemIndex = this.findDefaultSelectionIndex(this.selectableItems, this.textboxFormModel.value);
2973
+ this.selectedItem = this.selectableItems[this.selectedItemIndex] || null;
2940
2974
  this.scrollMenu(this.selectedItemIndex);
2941
2975
  }
2942
2976
  else {
@@ -3130,18 +3164,11 @@ class ComboboxComponent extends FormControlBase {
3130
3164
  if (this.selectedItemIndex === -1 && this.addNewButton && !this.addNewButton.nativeElement.hidden && !this.addNewSelected) {
3131
3165
  this.addNewSelected = true;
3132
3166
  }
3133
- else if (this.selectedItemIndex < this.filteredOptions.length - 1) {
3167
+ else if (this.selectedItemIndex < this.selectableItems.length - 1) {
3168
+ this.selectedItemIndex++;
3134
3169
  if (this.addNewSelected) {
3135
3170
  this.addNewSelected = false;
3136
3171
  }
3137
- //if the last item is a heading and we are on the second last item, we shouldn't increment the selectedItemIndex because that would select the heading
3138
- if (!(this.selectedItemIndex == this.filteredOptions.length - 2 && this.filteredOptions[this.selectedItemIndex + 1].display === 'heading')) {
3139
- this.selectedItemIndex++;
3140
- // Skip any in-menu heading items
3141
- if (this.selectedItemIndex < this.filteredOptions.length - 1 && this.filteredOptions[this.selectedItemIndex].display === 'heading') {
3142
- this.selectedItemIndex++;
3143
- }
3144
- }
3145
3172
  }
3146
3173
  break;
3147
3174
  case "ArrowUp":
@@ -3150,30 +3177,16 @@ class ComboboxComponent extends FormControlBase {
3150
3177
  this.addNewSelected = false;
3151
3178
  }
3152
3179
  else if (this.selectedItemIndex > -1) {
3153
- if (this.selectedItemIndex === 0 && this.addNewButton && !this.addNewButton.nativeElement.hidden) {
3154
- this.addNewSelected = true;
3155
- }
3156
3180
  this.selectedItemIndex--;
3157
- // Skip any in-menu heading items
3158
- if (this.selectedItemIndex > -1 && this.filteredOptions[this.selectedItemIndex].display === 'heading') {
3159
- this.selectedItemIndex--;
3160
- //if the selectedItemIndex is -1, that means the very first item was a heading and we just skipped over it.
3161
- if (this.selectedItemIndex == -1) {
3162
- //if there is an add new button we should select it. otherwise we should lock them at index 1 (right before the heading)
3163
- if (this.addNewButton && !this.addNewButton.nativeElement.hidden) {
3164
- this.addNewSelected = true;
3165
- }
3166
- else {
3167
- this.selectedItemIndex = 1;
3168
- }
3169
- }
3181
+ if (this.selectedItemIndex === -1 && this.addNewButton && !this.addNewButton.nativeElement.hidden) {
3182
+ this.addNewSelected = true;
3170
3183
  }
3171
3184
  }
3172
3185
  break;
3173
3186
  default:
3174
3187
  return;
3175
3188
  }
3176
- this.selectedItem = this.filteredOptions[this.selectedItemIndex];
3189
+ this.selectedItem = this.selectableItems[this.selectedItemIndex];
3177
3190
  if (this.menuStatus === 'hidden') {
3178
3191
  this.setFormModelValue(this.selectedItem);
3179
3192
  }
@@ -3192,10 +3205,16 @@ class ComboboxComponent extends FormControlBase {
3192
3205
  filterOptionsArray(filterText) {
3193
3206
  let searchText = filterText.toLowerCase();
3194
3207
  if (filterText && filterText !== '') {
3195
- return this.options.filter(item => {
3196
- return item.label.toLowerCase().indexOf(searchText) >= 0 ||
3197
- (item.caption && item.caption.toLowerCase().indexOf(searchText) >= 0);
3198
- });
3208
+ const matchesSearch = (item) => item.label.toLowerCase().indexOf(searchText) >= 0 || (item.caption && item.caption.toLowerCase().indexOf(searchText) >= 0);
3209
+ return this.options.reduce((filteredItems, item) => {
3210
+ if (!item.items?.length && matchesSearch(item)) {
3211
+ filteredItems.push(item);
3212
+ }
3213
+ else if (item.items?.length && (item.display === 'heading' || item.display === 'divided-section')) {
3214
+ filteredItems.push({ ...item, items: item.items.filter(matchesSearch) });
3215
+ }
3216
+ return filteredItems;
3217
+ }, []);
3199
3218
  }
3200
3219
  else {
3201
3220
  return this.options;
@@ -3212,7 +3231,7 @@ class ComboboxComponent extends FormControlBase {
3212
3231
  findSelectedItemIndex(item) {
3213
3232
  let itemToFind = item ? item : this.selectedItem;
3214
3233
  if (itemToFind) {
3215
- return this.filteredOptions.findIndex(item => {
3234
+ return this.selectableItems.findIndex(item => {
3216
3235
  if (item.id && itemToFind.id) {
3217
3236
  return item.label === itemToFind.label && item.id === itemToFind.id;
3218
3237
  }
@@ -3238,7 +3257,7 @@ class ComboboxComponent extends FormControlBase {
3238
3257
  this.textboxFormModel.setValue(data.selectedLabel || data.label);
3239
3258
  this.selectedItemIndex = this.findSelectedItemIndex(data);
3240
3259
  if (this.selectedItemIndex >= 0) {
3241
- this.selectedItem = this.filteredOptions[this.selectedItemIndex];
3260
+ this.selectedItem = this.selectableItems[this.selectedItemIndex];
3242
3261
  }
3243
3262
  }
3244
3263
  else {
@@ -3259,10 +3278,10 @@ class ComboboxComponent extends FormControlBase {
3259
3278
  if (this.selectedItemIndex > -1 && this.selectedItem) {
3260
3279
  // The menu component will automatically generate ids for the menu items if they dont have them,
3261
3280
  // so they need to be accounted for here since the combobox doesn't know about those ids
3262
- let itemSelector = this.selectedItem.id ? `#${this.selectedItem.id}` : `#${this.id}_menu_item${this.selectedItemIndex}`;
3281
+ let itemId = this.selectedItem.id ? this.selectedItem.id : MenuComponent.getIndexedItemId(this.filteredOptions, this.selectedItem, `${this.id}_menu`);
3263
3282
  //trigger the scrolling after a tick to allow the menu to be up to date (and pending mask cleared) before measuring
3264
3283
  setTimeout(() => {
3265
- this.scrollService.scrollItemCentered(`#${this.id}_menu_list`, itemSelector);
3284
+ this.scrollService.scrollItemCentered(`#${this.id}_menu_list`, `#${itemId}`);
3266
3285
  }, 0);
3267
3286
  }
3268
3287
  else {
@@ -3428,10 +3447,10 @@ class ComboboxComponent extends FormControlBase {
3428
3447
  findDefaultSelectionIndex(options, searchText) {
3429
3448
  let index = -1;
3430
3449
  if (options && options.length) {
3431
- index = options.findIndex(option => option != null && option.display !== 'heading');
3450
+ index = options.findIndex(option => option != null);
3432
3451
  if (searchText) {
3433
3452
  searchText = searchText.toLowerCase().trim();
3434
- let exactMatchIndex = options.findIndex(option => option.label.toLowerCase() == searchText && option.display !== 'heading');
3453
+ let exactMatchIndex = options.findIndex(option => option.label.toLowerCase() == searchText);
3435
3454
  if (exactMatchIndex >= 0) {
3436
3455
  index = exactMatchIndex;
3437
3456
  }