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

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