@energycap/components 0.46.14 → 0.46.16

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.
@@ -16,8 +16,8 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core';
16
16
  import { ClipboardService } from 'ngx-clipboard';
17
17
  import { Subject, of, lastValueFrom, forkJoin, merge, fromEvent, EMPTY, timer, interval, race, ReplaySubject } from 'rxjs';
18
18
  import { takeUntil, switchMap, delay, distinctUntilChanged, filter, take, debounceTime, skipUntil, tap, pluck, map, catchError, skipWhile } from 'rxjs/operators';
19
- import moment from 'moment';
20
- import { range, cloneDeep, isEqual, uniqueId, sortBy, orderBy, get, upperFirst } from 'lodash';
19
+ import { DateTime, Info } from 'luxon';
20
+ import { range, cloneDeep, isEqual, uniqueId, sortBy, orderBy, get, upperFirst } from 'lodash-es';
21
21
  import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
22
22
  import Popper from 'popper.js';
23
23
 
@@ -368,11 +368,11 @@ class BannerComponent {
368
368
  }
369
369
  }
370
370
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BannerComponent, deps: [{ token: CacheService }], target: i0.ɵɵFactoryTarget.Component }); }
371
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: BannerComponent, isStandalone: false, selector: "ec-banner", inputs: { hidden: "hidden", id: "id", type: "type", bannerStyle: "bannerStyle", title: "title", text: "text", list: "list", showCloseBtn: "showCloseBtn", autoHideOnClose: "autoHideOnClose", customIcon: "customIcon", rememberClosed: "rememberClosed" }, outputs: { closed: "closed" }, host: { properties: { "hidden": "this.hidden" } }, usesOnChanges: true, ngImport: i0, template: "<div id=\"banner_{{id}}\" \n class=\"banner {{type}} {{bannerStyle}}\"\n [attr.role]=\"type === 'error' || type === 'warning' ? 'alert' : (type === 'info' || type === 'success' ? 'status' : null)\">\n @if (showCloseBtn) {\n <ec-button id=\"banner{{id}}_close\"\n type=\"icon\"\n icon=\"icon-cancel\"\n ariaLabel=\"Banner_CloseButtonLabel\"\n (clicked)=\"close()\">\n </ec-button>\n }\n <div class=\"banner-content d-flex text-body-1\">\n <i class=\"ec-icon {{icon}}\"></i>\n <div class=\"ml-2\">\n @if (title) {\n <p class=\"title mb-0\">\n {{title}}\n </p>\n }\n @if (text) {\n <p class=\"text mb-0\">{{text}}</p>\n }\n\n @if (list?.length) {\n <ul class=\"list mb-0\">\n @for (item of list; track item) {\n <li>{{item}}</li>\n }\n </ul>\n }\n <ng-content></ng-content>\n </div>\n </div>\n\n</div>\n", styles: [":host{display:flex}:host(.border-bottom-0) .banner{border-bottom:0;border-radius:var(--ec-border-radius-card)}.banner{display:flex;flex-direction:column;flex:1 1;min-height:2.5rem;position:relative;overflow-y:auto;background-color:var(--ec-banner-background-color, var(--ec-background-color-info));border-radius:var(--ec-banner-border-radius, var(--ec-border-radius-card));border:1px solid var(--ec-banner-border-color, var(--ec-color-info))}.banner-content{align-items:var(--ec-banner-align-items-content, normal);padding:.5rem 1rem;flex:none;margin:auto 0;color:var(--ec-banner-color, var(--ec-color-secondary-dark))}.banner-content ::ng-deep p{line-height:inherit}.ec-icon{color:var(--ec-banner-icon-color, var(--ec-color-info));font-size:1.125rem}ec-button{--ec-button-color-icon-hover: var(--ec-color-primary-dark);--ec-background-color-hover: rgba(26, 26, 35, .1);position:absolute;top:.1875rem;right:.25rem}ec-button+.banner-content{padding-right:2.75rem}.title{font-weight:700}.list{margin:0;padding-left:2em}.text+.list{margin-top:1em}.info:not(.toast){--ec-banner-background-color: var(--ec-background-color-info);--ec-banner-border-color: var(--ec-color-info);--ec-banner-icon-color: var(--ec-color-info)}.warning:not(.toast){--ec-banner-background-color: var(--ec-background-color-caution);--ec-banner-border-color: var(--ec-color-caution);--ec-banner-icon-color: var(--ec-color-caution)}.success:not(.toast){--ec-banner-background-color: var(--ec-background-color-success);--ec-banner-border-color: var(--ec-color-success);--ec-banner-icon-color: var(--ec-color-success)}.error:not(.toast){--ec-banner-background-color: var(--ec-background-color-danger);--ec-banner-border-color: var(--ec-color-danger);--ec-banner-icon-color: var(--ec-color-danger)}.pinned{border-radius:0;border-left:0;border-right:0}.promo:not(.toast){--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-secondary-light);--ec-color-link: var(--ec-color-link-light);border:none;background:var(--ec-background-color-promo)}.promo:not(.toast) ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15)}.instructional:not(.toast){--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-secondary-light);--ec-color-link: var(--ec-color-link-light);border:none;background:var(--ec-background-color-instructional)}.instructional:not(.toast) ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15)}.toast{--ec-banner-background-color: var(--ec-color-gray-8);--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-primary-light);--ec-color-link: var(--ec-color-link-light);border:0;box-shadow:var(--ec-box-shadow)}.toast ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15);top:.25rem;right:.25rem}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "ec-button", inputs: ["id", "disabled", "lockButton", "icon", "iconColor", "label", "ariaLabel", "ariaExpanded", "ariaHaspopup", "ariaActivedescendant", "ariaControls", "badge", "tabindex", "type", "pending", "pendingIcon", "customTemplate", "isSubmit", "autofocus"], outputs: ["clicked"] }] }); }
371
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: BannerComponent, isStandalone: false, selector: "ec-banner", inputs: { hidden: "hidden", id: "id", type: "type", bannerStyle: "bannerStyle", title: "title", text: "text", list: "list", showCloseBtn: "showCloseBtn", autoHideOnClose: "autoHideOnClose", customIcon: "customIcon", rememberClosed: "rememberClosed" }, outputs: { closed: "closed" }, host: { properties: { "hidden": "this.hidden" } }, usesOnChanges: true, ngImport: i0, template: "<div id=\"banner_{{id}}\" \n class=\"banner {{type}} {{bannerStyle}}\"\n [attr.role]=\"type === 'error' || type === 'warning' ? 'alert' : (type === 'info' || type === 'success' ? 'status' : null)\">\n @if (showCloseBtn) {\n <ec-button id=\"banner{{id}}_close\"\n type=\"icon\"\n icon=\"icon-cancel\"\n ariaLabel=\"Banner_CloseButtonLabel\"\n (clicked)=\"close()\">\n </ec-button>\n }\n <div class=\"banner-content d-flex text-body-1\">\n <i class=\"ec-icon {{icon}}\" aria-hidden=\"true\"></i>\n <div class=\"ml-2\">\n @if (title) {\n <p class=\"title mb-0\">\n {{title}}\n </p>\n }\n @if (text) {\n <p class=\"text mb-0\">{{text}}</p>\n }\n\n @if (list?.length) {\n <ul class=\"list mb-0\">\n @for (item of list; track item) {\n <li>{{item}}</li>\n }\n </ul>\n }\n <ng-content></ng-content>\n </div>\n </div>\n\n</div>\n", styles: [":host{display:flex}:host(.border-bottom-0) .banner{border-bottom:0;border-radius:var(--ec-border-radius-card)}.banner{display:flex;flex-direction:column;flex:1 1;min-height:2.5rem;position:relative;overflow-y:auto;background-color:var(--ec-banner-background-color, var(--ec-background-color-info));border-radius:var(--ec-banner-border-radius, var(--ec-border-radius-card));border:1px solid var(--ec-banner-border-color, var(--ec-color-info))}.banner-content{align-items:var(--ec-banner-align-items-content, normal);padding:.5rem 1rem;flex:none;margin:auto 0;color:var(--ec-banner-color, var(--ec-color-secondary-dark))}.banner-content ::ng-deep p{line-height:inherit}.ec-icon{color:var(--ec-banner-icon-color, var(--ec-color-info));font-size:1.125rem}ec-button{--ec-button-color-icon-hover: var(--ec-color-primary-dark);--ec-background-color-hover: rgba(26, 26, 35, .1);position:absolute;top:.1875rem;right:.25rem}ec-button+.banner-content{padding-right:2.75rem}.title{font-weight:700}.list{margin:0;padding-left:2em}.text+.list{margin-top:1em}.info:not(.toast){--ec-banner-background-color: var(--ec-background-color-info);--ec-banner-border-color: var(--ec-color-info);--ec-banner-icon-color: var(--ec-color-info)}.warning:not(.toast){--ec-banner-background-color: var(--ec-background-color-caution);--ec-banner-border-color: var(--ec-color-caution);--ec-banner-icon-color: var(--ec-color-caution)}.success:not(.toast){--ec-banner-background-color: var(--ec-background-color-success);--ec-banner-border-color: var(--ec-color-success);--ec-banner-icon-color: var(--ec-color-success)}.error:not(.toast){--ec-banner-background-color: var(--ec-background-color-danger);--ec-banner-border-color: var(--ec-color-danger);--ec-banner-icon-color: var(--ec-color-danger)}.pinned{border-radius:0;border-left:0;border-right:0}.promo:not(.toast){--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-secondary-light);--ec-color-link: var(--ec-color-link-light);border:none;background:var(--ec-background-color-promo)}.promo:not(.toast) ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15)}.instructional:not(.toast){--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-secondary-light);--ec-color-link: var(--ec-color-link-light);border:none;background:var(--ec-background-color-instructional)}.instructional:not(.toast) ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15)}.toast{--ec-banner-background-color: var(--ec-color-gray-8);--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-primary-light);--ec-color-link: var(--ec-color-link-light);border:0;box-shadow:var(--ec-box-shadow)}.toast ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15);top:.25rem;right:.25rem}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "ec-button", inputs: ["id", "disabled", "lockButton", "icon", "iconColor", "label", "ariaLabel", "ariaExpanded", "ariaHaspopup", "ariaActivedescendant", "ariaControls", "badge", "tabindex", "type", "pending", "pendingIcon", "customTemplate", "isSubmit", "autofocus"], outputs: ["clicked"] }] }); }
372
372
  }
373
373
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BannerComponent, decorators: [{
374
374
  type: Component,
375
- args: [{ selector: 'ec-banner', standalone: false, template: "<div id=\"banner_{{id}}\" \n class=\"banner {{type}} {{bannerStyle}}\"\n [attr.role]=\"type === 'error' || type === 'warning' ? 'alert' : (type === 'info' || type === 'success' ? 'status' : null)\">\n @if (showCloseBtn) {\n <ec-button id=\"banner{{id}}_close\"\n type=\"icon\"\n icon=\"icon-cancel\"\n ariaLabel=\"Banner_CloseButtonLabel\"\n (clicked)=\"close()\">\n </ec-button>\n }\n <div class=\"banner-content d-flex text-body-1\">\n <i class=\"ec-icon {{icon}}\"></i>\n <div class=\"ml-2\">\n @if (title) {\n <p class=\"title mb-0\">\n {{title}}\n </p>\n }\n @if (text) {\n <p class=\"text mb-0\">{{text}}</p>\n }\n\n @if (list?.length) {\n <ul class=\"list mb-0\">\n @for (item of list; track item) {\n <li>{{item}}</li>\n }\n </ul>\n }\n <ng-content></ng-content>\n </div>\n </div>\n\n</div>\n", styles: [":host{display:flex}:host(.border-bottom-0) .banner{border-bottom:0;border-radius:var(--ec-border-radius-card)}.banner{display:flex;flex-direction:column;flex:1 1;min-height:2.5rem;position:relative;overflow-y:auto;background-color:var(--ec-banner-background-color, var(--ec-background-color-info));border-radius:var(--ec-banner-border-radius, var(--ec-border-radius-card));border:1px solid var(--ec-banner-border-color, var(--ec-color-info))}.banner-content{align-items:var(--ec-banner-align-items-content, normal);padding:.5rem 1rem;flex:none;margin:auto 0;color:var(--ec-banner-color, var(--ec-color-secondary-dark))}.banner-content ::ng-deep p{line-height:inherit}.ec-icon{color:var(--ec-banner-icon-color, var(--ec-color-info));font-size:1.125rem}ec-button{--ec-button-color-icon-hover: var(--ec-color-primary-dark);--ec-background-color-hover: rgba(26, 26, 35, .1);position:absolute;top:.1875rem;right:.25rem}ec-button+.banner-content{padding-right:2.75rem}.title{font-weight:700}.list{margin:0;padding-left:2em}.text+.list{margin-top:1em}.info:not(.toast){--ec-banner-background-color: var(--ec-background-color-info);--ec-banner-border-color: var(--ec-color-info);--ec-banner-icon-color: var(--ec-color-info)}.warning:not(.toast){--ec-banner-background-color: var(--ec-background-color-caution);--ec-banner-border-color: var(--ec-color-caution);--ec-banner-icon-color: var(--ec-color-caution)}.success:not(.toast){--ec-banner-background-color: var(--ec-background-color-success);--ec-banner-border-color: var(--ec-color-success);--ec-banner-icon-color: var(--ec-color-success)}.error:not(.toast){--ec-banner-background-color: var(--ec-background-color-danger);--ec-banner-border-color: var(--ec-color-danger);--ec-banner-icon-color: var(--ec-color-danger)}.pinned{border-radius:0;border-left:0;border-right:0}.promo:not(.toast){--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-secondary-light);--ec-color-link: var(--ec-color-link-light);border:none;background:var(--ec-background-color-promo)}.promo:not(.toast) ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15)}.instructional:not(.toast){--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-secondary-light);--ec-color-link: var(--ec-color-link-light);border:none;background:var(--ec-background-color-instructional)}.instructional:not(.toast) ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15)}.toast{--ec-banner-background-color: var(--ec-color-gray-8);--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-primary-light);--ec-color-link: var(--ec-color-link-light);border:0;box-shadow:var(--ec-box-shadow)}.toast ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15);top:.25rem;right:.25rem}\n"] }]
375
+ args: [{ selector: 'ec-banner', standalone: false, template: "<div id=\"banner_{{id}}\" \n class=\"banner {{type}} {{bannerStyle}}\"\n [attr.role]=\"type === 'error' || type === 'warning' ? 'alert' : (type === 'info' || type === 'success' ? 'status' : null)\">\n @if (showCloseBtn) {\n <ec-button id=\"banner{{id}}_close\"\n type=\"icon\"\n icon=\"icon-cancel\"\n ariaLabel=\"Banner_CloseButtonLabel\"\n (clicked)=\"close()\">\n </ec-button>\n }\n <div class=\"banner-content d-flex text-body-1\">\n <i class=\"ec-icon {{icon}}\" aria-hidden=\"true\"></i>\n <div class=\"ml-2\">\n @if (title) {\n <p class=\"title mb-0\">\n {{title}}\n </p>\n }\n @if (text) {\n <p class=\"text mb-0\">{{text}}</p>\n }\n\n @if (list?.length) {\n <ul class=\"list mb-0\">\n @for (item of list; track item) {\n <li>{{item}}</li>\n }\n </ul>\n }\n <ng-content></ng-content>\n </div>\n </div>\n\n</div>\n", styles: [":host{display:flex}:host(.border-bottom-0) .banner{border-bottom:0;border-radius:var(--ec-border-radius-card)}.banner{display:flex;flex-direction:column;flex:1 1;min-height:2.5rem;position:relative;overflow-y:auto;background-color:var(--ec-banner-background-color, var(--ec-background-color-info));border-radius:var(--ec-banner-border-radius, var(--ec-border-radius-card));border:1px solid var(--ec-banner-border-color, var(--ec-color-info))}.banner-content{align-items:var(--ec-banner-align-items-content, normal);padding:.5rem 1rem;flex:none;margin:auto 0;color:var(--ec-banner-color, var(--ec-color-secondary-dark))}.banner-content ::ng-deep p{line-height:inherit}.ec-icon{color:var(--ec-banner-icon-color, var(--ec-color-info));font-size:1.125rem}ec-button{--ec-button-color-icon-hover: var(--ec-color-primary-dark);--ec-background-color-hover: rgba(26, 26, 35, .1);position:absolute;top:.1875rem;right:.25rem}ec-button+.banner-content{padding-right:2.75rem}.title{font-weight:700}.list{margin:0;padding-left:2em}.text+.list{margin-top:1em}.info:not(.toast){--ec-banner-background-color: var(--ec-background-color-info);--ec-banner-border-color: var(--ec-color-info);--ec-banner-icon-color: var(--ec-color-info)}.warning:not(.toast){--ec-banner-background-color: var(--ec-background-color-caution);--ec-banner-border-color: var(--ec-color-caution);--ec-banner-icon-color: var(--ec-color-caution)}.success:not(.toast){--ec-banner-background-color: var(--ec-background-color-success);--ec-banner-border-color: var(--ec-color-success);--ec-banner-icon-color: var(--ec-color-success)}.error:not(.toast){--ec-banner-background-color: var(--ec-background-color-danger);--ec-banner-border-color: var(--ec-color-danger);--ec-banner-icon-color: var(--ec-color-danger)}.pinned{border-radius:0;border-left:0;border-right:0}.promo:not(.toast){--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-secondary-light);--ec-color-link: var(--ec-color-link-light);border:none;background:var(--ec-background-color-promo)}.promo:not(.toast) ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15)}.instructional:not(.toast){--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-secondary-light);--ec-color-link: var(--ec-color-link-light);border:none;background:var(--ec-background-color-instructional)}.instructional:not(.toast) ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15)}.toast{--ec-banner-background-color: var(--ec-color-gray-8);--ec-banner-color: var(--ec-color-primary-light);--ec-banner-icon-color: var(--ec-color-primary-light);--ec-color-link: var(--ec-color-link-light);border:0;box-shadow:var(--ec-box-shadow)}.toast ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15);top:.25rem;right:.25rem}\n"] }]
376
376
  }], ctorParameters: () => [{ type: CacheService }], propDecorators: { hidden: [{
377
377
  type: HostBinding,
378
378
  args: ['hidden']
@@ -539,7 +539,7 @@ class DateTimeHelper {
539
539
  /** 3000-01-01 */
540
540
  static { this.endOfTime = new Date(3000, 0, 1); }
541
541
  /** 3000-01-01 UTC */
542
- static { this.endOfTimeUTC = moment.utc('3000-01-01').toDate(); }
542
+ static { this.endOfTimeUTC = DateTime.fromISO('3000-01-01', { zone: 'utc' }).toJSDate(); }
543
543
  /** Default effective date for new AccountMeter relationships: 2010-01-01 */
544
544
  static { this.defaultAccountMeterEffectiveDate = new Date(2010, 0, 1); }
545
545
  /** The default date that a newly created account becomes effective: 2010-01-01 */
@@ -581,20 +581,22 @@ class DateTimeHelper {
581
581
  "yyyyMMdd"
582
582
  ]; }
583
583
  /**
584
- * User input formats that we will parse into a date
585
- * when the users date format preference has month before day.
586
- * To be used with moment.js. Moment ignores non-alphanumeric characters
587
- * so dates like 2020/09/10 will be parsed the same as 2020-09-10
588
- */
589
- static { this.momentMonthFirstParseFormats = [
590
- 'M-D',
591
- 'MM-DD',
592
- 'M-D-YY',
593
- 'MM-DD-YY',
594
- 'MMDDYYYY',
595
- 'YYYYMMDD',
596
- 'YYYY-MM-DD',
597
- 'MM-DD-YYYY'
584
+ * Luxon parse formats for the month-first preference. Separator characters
585
+ * in the input are normalized to a hyphen before matching (see {@link parseWithFormats}),
586
+ * so the formats below intentionally use a single separator.
587
+ */
588
+ static { this.luxonMonthFirstParseFormats = [
589
+ 'M-d',
590
+ 'MM-dd',
591
+ 'M-d-yy',
592
+ 'MM-dd-yy',
593
+ 'MMddyyyy',
594
+ 'yyyyMMdd',
595
+ 'yyyy-MM-dd',
596
+ 'MM-dd-yyyy',
597
+ // A lone number is treated as a month; the day defaults to the 1st and the year to the current year
598
+ // (e.g. "11" -> Nov 1 of this year). Listed last so it never shadows the more specific formats above.
599
+ 'M'
598
600
  ]; }
599
601
  /**
600
602
  * User input formats that we will parse into a date
@@ -618,44 +620,58 @@ class DateTimeHelper {
618
620
  "yyyyDDmm"
619
621
  ]; }
620
622
  /**
621
- * User input formats that we will parse into a date
622
- * when the users date format preference has day before month.
623
- * To be used with moment.js. Moment ignores non-alphanumeric characters
624
- * so dates like 2020/09/10 will be parsed the same as 2020-09-10
625
- */
626
- static { this.momentDateFirstParseFormats = [
627
- 'D-M',
628
- 'DD-MM',
629
- 'D-M-YY',
630
- 'DD-MM-YY',
631
- 'DDMMYYYY',
632
- 'YYYYMMDD',
633
- 'YYYY-MM-DD',
634
- 'DD-MM-YYYY'
623
+ * Luxon parse formats for the day-first preference. Separator characters
624
+ * in the input are normalized to a hyphen before matching (see {@link parseWithFormats}).
625
+ */
626
+ static { this.luxonDateFirstParseFormats = [
627
+ 'd-M',
628
+ 'dd-MM',
629
+ 'd-M-yy',
630
+ 'dd-MM-yy',
631
+ 'ddMMyyyy',
632
+ 'yyyyMMdd',
633
+ 'yyyy-MM-dd',
634
+ 'dd-MM-yyyy',
635
+ // A lone number is treated as a month; the day defaults to the 1st and the year to the current year
636
+ // (e.g. "11" -> Nov 1 of this year). Listed last so it never shadows the more specific formats above.
637
+ 'M'
635
638
  ]; }
636
639
  /**
637
- * Formats used to parse dates that are in the medium or long format by moment js.
638
- * These are shared by date-first and month-first date formats. The date can't be
639
- * parsed differently based on the date preference since the month is not numeric.
640
- * This is not an exhaustive list of formats, but it should cover most common formats.
641
- */
642
- static { this.momentMedAndLongDateFormats = [
643
- 'DD MMM',
644
- 'MMM DD',
645
- 'DD MMM YY',
646
- 'DD MMM YYYY',
647
- 'DD MMMM YYYY',
648
- 'MMM D, YY',
649
- 'MMM D, YYYY',
650
- 'MMM DD, YYYY',
651
- 'MMMM D, YYYY',
652
- 'MMMM DD, YYYY',
653
- 'MMM YY',
654
- 'MMM YYYY',
655
- 'MMMM YYYY',
656
- 'MMM, YY',
657
- 'MMM, YYYY',
658
- 'MMMM, YYYY',
640
+ * Luxon parse formats for medium/long date strings (e.g. "Jan 5, 2024").
641
+ * Shared by date-first and month-first preferences because the month is
642
+ * not numeric, so the order is unambiguous.
643
+ */
644
+ static { this.luxonMedAndLongDateFormats = [
645
+ // Moment was forgiving with leading zeros (`DD` matched both `9` and `09`);
646
+ // Luxon's `dd` is strict, so single-digit `d` variants are listed explicitly.
647
+ 'd MMM',
648
+ 'dd MMM',
649
+ 'MMM d',
650
+ 'MMM dd',
651
+ 'd MMM yy',
652
+ 'dd MMM yy',
653
+ 'd MMM yyyy',
654
+ 'dd MMM yyyy',
655
+ 'd MMMM yyyy',
656
+ 'dd MMMM yyyy',
657
+ 'MMM d, yy',
658
+ 'MMM d, yyyy',
659
+ 'MMM dd, yyyy',
660
+ 'MMMM d, yyyy',
661
+ 'MMMM dd, yyyy',
662
+ // Space-separated month-day-year without a comma (e.g. "Apr 21 1998", "April 21 1998").
663
+ 'MMM d yy',
664
+ 'MMM dd yy',
665
+ 'MMM d yyyy',
666
+ 'MMM dd yyyy',
667
+ 'MMMM d yyyy',
668
+ 'MMMM dd yyyy',
669
+ 'MMM yy',
670
+ 'MMM yyyy',
671
+ 'MMMM yyyy',
672
+ 'MMM, yy',
673
+ 'MMM, yyyy',
674
+ 'MMMM, yyyy',
659
675
  ]; }
660
676
  /**
661
677
  * Format to display the time portion of a datetime
@@ -664,6 +680,103 @@ class DateTimeHelper {
664
680
  ///////////////////////////////////////////////////////
665
681
  // METHODS
666
682
  ///////////////////////////////////////////////////////
683
+ /**
684
+ * Convert any date-like value into a Luxon {@link DateTime}. Strings are parsed
685
+ * as ISO 8601; numbers are treated as epoch milliseconds. Returns an invalid
686
+ * DateTime when the input is null/undefined.
687
+ */
688
+ static toDateTime(value) {
689
+ if (value === null || value === undefined) {
690
+ return DateTime.invalid('null or undefined');
691
+ }
692
+ if (DateTime.isDateTime(value)) {
693
+ return value;
694
+ }
695
+ if (value instanceof Date) {
696
+ return DateTime.fromJSDate(value);
697
+ }
698
+ if (typeof value === 'number') {
699
+ return DateTime.fromMillis(value);
700
+ }
701
+ return DateTime.fromISO(value);
702
+ }
703
+ /**
704
+ * Translate a moment-style format string (e.g. `MM/DD/YYYY HH:mm:ss A`) into
705
+ * the Luxon equivalent (`MM/dd/yyyy HH:mm:ss a`). Use this when format strings
706
+ * originate from external sources (such as user preferences) that still use
707
+ * moment tokens.
708
+ *
709
+ * Only the tokens currently in use across this library are translated:
710
+ * `Y`→`y`, `D`→`d`, `A`→`a`. `M`, `H`, `h`, `m`, `s` are identical between
711
+ * the two libraries.
712
+ */
713
+ static momentToLuxonFormat(format) {
714
+ return format
715
+ .replace(/Y/g, 'y')
716
+ .replace(/D/g, 'd')
717
+ .replace(/A/g, 'a');
718
+ }
719
+ /**
720
+ * Format a date-like value using a moment-style format string. The format
721
+ * string is automatically translated to Luxon tokens, so callers can keep
722
+ * passing the same preference strings (e.g. `MM/DD/YYYY HH:mm:ss`).
723
+ *
724
+ * When `displayInUTC` is true, string inputs without zone info are parsed
725
+ * as UTC (matching `moment.utc(str)`), and all other inputs are shifted to
726
+ * the UTC zone for display. Returns an empty string for invalid input.
727
+ *
728
+ * Moment's lowercase `a` token produces lowercase `am`/`pm`, but Luxon's
729
+ * `a` is locale-aware and emits uppercase `AM`/`PM` in en-US. When the
730
+ * incoming format uses lowercase `a` (and not uppercase `A`), the meridiem
731
+ * in the output is lowercased to preserve the moment contract for user
732
+ * preferences like `h:mm:ss a` that are stored externally.
733
+ */
734
+ static formatDate(date, momentFormat, displayInUTC = false) {
735
+ let dt;
736
+ if (displayInUTC && typeof date === 'string') {
737
+ dt = DateTime.fromISO(date, { zone: 'utc' });
738
+ }
739
+ else {
740
+ dt = this.toDateTime(date);
741
+ if (displayInUTC) {
742
+ dt = dt.setZone('utc');
743
+ }
744
+ }
745
+ if (!dt.isValid)
746
+ return '';
747
+ let result = dt.toFormat(this.momentToLuxonFormat(momentFormat));
748
+ if (momentFormat.includes('a') && !momentFormat.includes('A')) {
749
+ result = result.replace(/AM|PM/g, m => m.toLowerCase());
750
+ }
751
+ return result;
752
+ }
753
+ /**
754
+ * Parse a string against a list of format patterns. Returns the first
755
+ * successful parse as a {@link DateTime} (which may be invalid if none match).
756
+ *
757
+ * Format strings may use either Luxon or moment tokens — each pattern is
758
+ * run through {@link momentToLuxonFormat} before matching, so callers can
759
+ * keep passing arrays like `['M/D/YYYY']` from previously moment-based code.
760
+ *
761
+ * Mirrors moment's behavior of ignoring separator characters by normalizing
762
+ * `/`, `.` and `-` in both input and patterns before matching.
763
+ */
764
+ static parseWithFormats(value, formats) {
765
+ const normalize = (s) => s.replace(/[\/.\-]/g, '-');
766
+ // Strip surrounding whitespace and separators from the input so a partial entry with a
767
+ // trailing (or leading) separator still matches — e.g. "11/" normalizes to "11", which
768
+ // parses via the month-only format to Nov 1 of the current year. Internal separators are
769
+ // preserved, so full dates like "Apr 21 1998" are unaffected.
770
+ const normalizedValue = normalize(value).replace(/^[\s-]+|[\s-]+$/g, '');
771
+ for (const format of formats) {
772
+ const luxonFormat = this.momentToLuxonFormat(format);
773
+ const dt = DateTime.fromFormat(normalizedValue, normalize(luxonFormat));
774
+ if (dt.isValid) {
775
+ return dt;
776
+ }
777
+ }
778
+ return DateTime.invalid('no matching format');
779
+ }
667
780
  /**
668
781
  * Return true if @see date is equal to the @see endOfTime
669
782
  */
@@ -675,7 +788,7 @@ class DateTimeHelper {
675
788
  * Compare two dates and return true if they represent the same year, month, and day (time is ignored).
676
789
  */
677
790
  static isSameDay(date1, date2) {
678
- return moment(date1).isSame(moment(date2), 'day');
791
+ return this.toDateTime(date1).hasSame(this.toDateTime(date2), 'day');
679
792
  }
680
793
  /**
681
794
  * Get the parse formats based on the given date format. If no date format is
@@ -694,19 +807,19 @@ class DateTimeHelper {
694
807
  return dateFormat ? map[dateFormat] : this.monthFirstParseFormats;
695
808
  }
696
809
  /**
697
- * Get the moment parse formats based on the given date format. If no date format is
810
+ * Get the Luxon parse formats based on the given date format. If no date format is
698
811
  * given, or we don't recognize the format, return the month first parse formats.
699
812
  */
700
- static getMomentParseFormats(dateFormat) {
813
+ static getLuxonParseFormats(dateFormat) {
701
814
  let map = {
702
- 'MM/DD/YYYY': [...this.momentMonthFirstParseFormats, ...this.momentMedAndLongDateFormats],
703
- 'MM-DD-YYYY': [...this.momentMonthFirstParseFormats, ...this.momentMedAndLongDateFormats],
704
- 'DD/MM/YYYY': [...this.momentDateFirstParseFormats, ...this.momentMedAndLongDateFormats],
705
- 'DD-MM-YYYY': [...this.momentDateFirstParseFormats, ...this.momentMedAndLongDateFormats],
706
- 'YYYY/MM/DD': [...this.momentMonthFirstParseFormats, ...this.momentMedAndLongDateFormats],
707
- 'YYYY-MM-DD': [...this.momentMonthFirstParseFormats, ...this.momentMedAndLongDateFormats]
815
+ 'MM/DD/YYYY': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats],
816
+ 'MM-DD-YYYY': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats],
817
+ 'DD/MM/YYYY': [...this.luxonDateFirstParseFormats, ...this.luxonMedAndLongDateFormats],
818
+ 'DD-MM-YYYY': [...this.luxonDateFirstParseFormats, ...this.luxonMedAndLongDateFormats],
819
+ 'YYYY/MM/DD': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats],
820
+ 'YYYY-MM-DD': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats]
708
821
  };
709
- return dateFormat ? map[dateFormat] : this.momentMonthFirstParseFormats;
822
+ return dateFormat ? map[dateFormat] : this.luxonMonthFirstParseFormats;
710
823
  }
711
824
  /**
712
825
  * Get relationship start date (favoring date in partialFormState if exists)
@@ -729,28 +842,23 @@ class DateTimeHelper {
729
842
  return null;
730
843
  }
731
844
  static isDateBetween(date, startDate, endDate, includeStart, includeEnd) {
732
- let inclusivity = '()';
733
- if (includeStart && includeEnd) {
734
- inclusivity = '[]';
735
- }
736
- else if (includeStart) {
737
- inclusivity = '[)';
738
- }
739
- else if (includeEnd) {
740
- inclusivity = '(]';
741
- }
742
845
  // If a start date is not provide, we'll use our beginning of time instead
743
846
  startDate = startDate || this.beginningOfTime;
744
847
  // If an end date is not provided, we'll use our end of time instead
745
848
  endDate = endDate || this.endOfTime;
746
- return moment(date).isBetween(startDate, endDate, 'day', inclusivity);
849
+ const d = this.toDateTime(date).startOf('day');
850
+ const s = this.toDateTime(startDate).startOf('day');
851
+ const e = this.toDateTime(endDate).startOf('day');
852
+ const afterStart = includeStart ? d >= s : d > s;
853
+ const beforeEnd = includeEnd ? d <= e : d < e;
854
+ return afterStart && beforeEnd;
747
855
  }
748
856
  static stringToDate(dateString) {
749
857
  if (dateString === null || dateString === undefined) {
750
858
  return dateString;
751
859
  }
752
860
  else {
753
- return moment(dateString).toDate();
861
+ return DateTime.fromISO(dateString).toJSDate();
754
862
  }
755
863
  }
756
864
  }
@@ -764,15 +872,15 @@ class DateInputSelectionStrategyBase {
764
872
  if (!value) {
765
873
  return null;
766
874
  }
767
- const date = moment(value, parseFormats);
768
- if (date.isValid()) {
875
+ let date = DateTimeHelper.parseWithFormats(value, parseFormats);
876
+ if (date.isValid) {
769
877
  // If we're in a temporary state (when the user is typing in a textbox), we need to set the year to the current year if it's before the min year
770
878
  // This prevents the calendars from showing unhelpful dates that are in the distant past before the user finishes typing in the date.
771
879
  // We don't do this modification when the real selection happens on textbox blur.
772
- if (opts?.shiftToCurrentYearIfBelow && date.isBefore(opts.shiftToCurrentYearIfBelow)) {
773
- date.set('year', moment().year());
880
+ if (opts?.shiftToCurrentYearIfBelow && date < DateTime.fromJSDate(opts.shiftToCurrentYearIfBelow)) {
881
+ date = date.set({ year: DateTime.now().year });
774
882
  }
775
- return date.toDate();
883
+ return date.toJSDate();
776
884
  }
777
885
  return null;
778
886
  }
@@ -854,7 +962,7 @@ class DaySelectionStrategy extends DateInputSelectionStrategyBase {
854
962
  getNextSelection(selection) {
855
963
  // Add a single day to the selection
856
964
  if (DateInput.isSelectionSingleDate(selection)) {
857
- return moment(selection).add(1, 'day').toDate();
965
+ return DateTime.fromJSDate(selection).plus({ days: 1 }).toJSDate();
858
966
  }
859
967
  // This shouldn't happen based on current date input flows, but if it does just return the selection as-is.
860
968
  return selection;
@@ -862,7 +970,7 @@ class DaySelectionStrategy extends DateInputSelectionStrategyBase {
862
970
  getPreviousSelection(selection) {
863
971
  // Subtract a single day from the selection
864
972
  if (DateInput.isSelectionSingleDate(selection)) {
865
- return moment(selection).subtract(1, 'day').toDate();
973
+ return DateTime.fromJSDate(selection).minus({ days: 1 }).toJSDate();
866
974
  }
867
975
  // This shouldn't happen based on current date input flows, but if it does just return the selection as-is.
868
976
  return selection;
@@ -893,19 +1001,19 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
893
1001
  // Update the start and end dates to match a 28-day range, keying off the end date if parseFromEnd is true
894
1002
  // parseFromEnd is true when a date is being entered from the end date textbox
895
1003
  if (opts?.parseFromEnd) {
896
- start = moment(end).subtract(27, 'days').toDate();
1004
+ start = DateTime.fromJSDate(end).minus({ days: 27 }).toJSDate();
897
1005
  }
898
1006
  else {
899
- end = moment(start).add(27, 'days').toDate();
1007
+ end = DateTime.fromJSDate(start).plus({ days: 27 }).toJSDate();
900
1008
  }
901
1009
  }
902
1010
  // If the start date is not provided, we'll use the end date to calculate the start date
903
1011
  if (!start && end && !opts?.preventAutoComplete) {
904
- start = moment(end).subtract(27, 'days').toDate();
1012
+ start = DateTime.fromJSDate(end).minus({ days: 27 }).toJSDate();
905
1013
  }
906
1014
  // If the end date is not provided, we'll use the start date to calculate the end date
907
1015
  if (!end && start && !opts?.preventAutoComplete) {
908
- end = moment(start).add(27, 'days').toDate();
1016
+ end = DateTime.fromJSDate(start).plus({ days: 27 }).toJSDate();
909
1017
  }
910
1018
  if (!start && !end) {
911
1019
  return null;
@@ -916,7 +1024,7 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
916
1024
  }
917
1025
  getSelectionFromDate(date) {
918
1026
  return {
919
- start: moment(date).subtract(27, 'days').toDate(),
1027
+ start: DateTime.fromJSDate(date).minus({ days: 27 }).toJSDate(),
920
1028
  end: date
921
1029
  };
922
1030
  }
@@ -926,7 +1034,7 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
926
1034
  if (!previousSelection.end && previousSelection.start) {
927
1035
  return {
928
1036
  start: previousSelection.start,
929
- end: moment(previousSelection.start).add(27, 'days').toDate()
1037
+ end: DateTime.fromJSDate(previousSelection.start).plus({ days: 27 }).toJSDate()
930
1038
  };
931
1039
  // If the range only has an end date, make the 28-day range end on that date
932
1040
  }
@@ -943,9 +1051,10 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
943
1051
  getNextSelection(selection) {
944
1052
  // Move the selection to the next 28 days
945
1053
  if (DateInput.isSelectionRange(selection) && selection.end) {
1054
+ const end = DateTime.fromJSDate(selection.end);
946
1055
  return {
947
- start: moment(selection.end).add(1, 'day').toDate(),
948
- end: moment(selection.end).add(28, 'days').toDate()
1056
+ start: end.plus({ days: 1 }).toJSDate(),
1057
+ end: end.plus({ days: 28 }).toJSDate()
949
1058
  };
950
1059
  }
951
1060
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
@@ -955,9 +1064,10 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
955
1064
  getPreviousSelection(selection) {
956
1065
  // Move the selection to the previous 28 days
957
1066
  if (DateInput.isSelectionRange(selection) && selection.start) {
1067
+ const start = DateTime.fromJSDate(selection.start);
958
1068
  return {
959
- start: moment(selection.start).subtract(28, 'days').toDate(),
960
- end: moment(selection.start).subtract(1, 'day').toDate()
1069
+ start: start.minus({ days: 28 }).toJSDate(),
1070
+ end: start.minus({ days: 1 }).toJSDate()
961
1071
  };
962
1072
  }
963
1073
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
@@ -990,19 +1100,19 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
990
1100
  // Update the start and end dates to match a 7-day range, keying off the end date if parseFromEnd is true
991
1101
  // parseFromEnd is true when a date is being entered from the end date textbox
992
1102
  if (opts?.parseFromEnd) {
993
- start = moment(end).subtract(6, 'days').toDate();
1103
+ start = DateTime.fromJSDate(end).minus({ days: 6 }).toJSDate();
994
1104
  }
995
1105
  else {
996
- end = moment(start).add(6, 'days').toDate();
1106
+ end = DateTime.fromJSDate(start).plus({ days: 6 }).toJSDate();
997
1107
  }
998
1108
  }
999
1109
  // If the start date is not provided, we'll use the end date to calculate the start date
1000
1110
  if (!start && end && !opts?.preventAutoComplete) {
1001
- start = moment(end).subtract(6, 'days').toDate();
1111
+ start = DateTime.fromJSDate(end).minus({ days: 6 }).toJSDate();
1002
1112
  }
1003
1113
  // If the end date is not provided, we'll use the start date to calculate the end date
1004
1114
  if (!end && start && !opts?.preventAutoComplete) {
1005
- end = moment(start).add(6, 'days').toDate();
1115
+ end = DateTime.fromJSDate(start).plus({ days: 6 }).toJSDate();
1006
1116
  }
1007
1117
  if (!start && !end) {
1008
1118
  return null;
@@ -1014,7 +1124,7 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1014
1124
  getSelectionFromDate(date) {
1015
1125
  // Base the selection on the given date, moving back 6 days
1016
1126
  return {
1017
- start: moment(date).subtract(6, 'days').toDate(),
1127
+ start: DateTime.fromJSDate(date).minus({ days: 6 }).toJSDate(),
1018
1128
  end: date
1019
1129
  };
1020
1130
  }
@@ -1024,7 +1134,7 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1024
1134
  if (!previousSelection.end && previousSelection.start) {
1025
1135
  return {
1026
1136
  start: previousSelection.start,
1027
- end: moment(previousSelection.start).add(6, 'days').toDate()
1137
+ end: DateTime.fromJSDate(previousSelection.start).plus({ days: 6 }).toJSDate()
1028
1138
  };
1029
1139
  // Otherwise, create the new selection based on the end date backwards
1030
1140
  }
@@ -1041,9 +1151,10 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1041
1151
  getNextSelection(selection) {
1042
1152
  // Shift the range forward by 7 days
1043
1153
  if (DateInput.isSelectionRange(selection) && selection.end) {
1154
+ const end = DateTime.fromJSDate(selection.end);
1044
1155
  return {
1045
- start: moment(selection.end).add(1, 'day').toDate(),
1046
- end: moment(selection.end).add(7, 'days').toDate()
1156
+ start: end.plus({ days: 1 }).toJSDate(),
1157
+ end: end.plus({ days: 7 }).toJSDate()
1047
1158
  };
1048
1159
  }
1049
1160
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
@@ -1053,9 +1164,10 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1053
1164
  getPreviousSelection(selection) {
1054
1165
  // Shift the range back 7 days
1055
1166
  if (DateInput.isSelectionRange(selection) && selection.start) {
1167
+ const start = DateTime.fromJSDate(selection.start);
1056
1168
  return {
1057
- start: moment(selection.start).subtract(7, 'days').toDate(),
1058
- end: moment(selection.start).subtract(1, 'day').toDate()
1169
+ start: start.minus({ days: 7 }).toJSDate(),
1170
+ end: start.minus({ days: 1 }).toJSDate()
1059
1171
  };
1060
1172
  }
1061
1173
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
@@ -1083,7 +1195,7 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1083
1195
  // A single date selection shouldn't happen in this mode but it's simple enough to handle
1084
1196
  const date = DateInput.isSelectionRange(selection) ? selection.start : selection;
1085
1197
  return {
1086
- textbox: moment(date).format('MMM, YYYY'),
1198
+ textbox: date ? DateTime.fromJSDate(date).toFormat('MMM, yyyy') : null,
1087
1199
  textbox2: null
1088
1200
  };
1089
1201
  }
@@ -1096,9 +1208,10 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1096
1208
  return this.getSelectionFromDate(date);
1097
1209
  }
1098
1210
  getSelectionFromDate(date) {
1211
+ const dt = DateTime.fromJSDate(date);
1099
1212
  return {
1100
- start: moment(date).startOf('month').toDate(),
1101
- end: moment(date).endOf('month').toDate()
1213
+ start: dt.startOf('month').toJSDate(),
1214
+ end: dt.endOf('month').toJSDate()
1102
1215
  };
1103
1216
  }
1104
1217
  getNewSelectionFromExisting(previousSelection) {
@@ -1118,8 +1231,8 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1118
1231
  getNextSelection(selection) {
1119
1232
  // Move the selection to the next month
1120
1233
  if (DateInput.isSelectionRange(selection) && selection.start) {
1121
- const date = moment(selection.start).add(1, 'month');
1122
- return this.getSelectionFromDate(date.toDate());
1234
+ const date = DateTime.fromJSDate(selection.start).plus({ months: 1 });
1235
+ return this.getSelectionFromDate(date.toJSDate());
1123
1236
  }
1124
1237
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
1125
1238
  // If it does, just return the selection as-is.
@@ -1128,8 +1241,8 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1128
1241
  getPreviousSelection(selection) {
1129
1242
  // Move the selection to the previous month
1130
1243
  if (DateInput.isSelectionRange(selection) && selection.start) {
1131
- const date = moment(selection.start).subtract(1, 'month');
1132
- return this.getSelectionFromDate(date.toDate());
1244
+ const date = DateTime.fromJSDate(selection.start).minus({ months: 1 });
1245
+ return this.getSelectionFromDate(date.toJSDate());
1133
1246
  }
1134
1247
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
1135
1248
  // If it does, just return the selection as-is.
@@ -1147,14 +1260,21 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1147
1260
  this.validViewModes = ['quarter', 'year'];
1148
1261
  this.showSecondaryTextbox = false;
1149
1262
  this.showSecondaryCalendar = false;
1263
+ /**
1264
+ * Format of the quarter range shown in the textbox (see {@link formatSelection}), e.g. "Jan–Mar, 2023".
1265
+ * Included in parsing so the displayed value round-trips (Luxon parsing is strict and would otherwise
1266
+ * reject it, clearing the selection when the textbox is blurred). Both month tokens land in the same
1267
+ * quarter, so parsing keys off whichever the parser resolves.
1268
+ */
1269
+ this.rangeParseFormats = ['MMM–MMM, yyyy', 'MMM–MMM, yy'];
1150
1270
  }
1151
1271
  formatSelection(selection) {
1152
1272
  // Format the the quarter range as "MMM–MMM, YYYY"
1153
1273
  // We only have one textbox in this mode
1154
1274
  if (DateInput.isSelectionRange(selection)) {
1155
- const start = moment(selection.start).format('MMM');
1156
- const end = moment(selection.end).format('MMM');
1157
- const year = moment(selection.end).format('YYYY');
1275
+ const start = selection.start ? DateTime.fromJSDate(selection.start).toFormat('MMM') : '';
1276
+ const end = selection.end ? DateTime.fromJSDate(selection.end).toFormat('MMM') : '';
1277
+ const year = selection.end ? DateTime.fromJSDate(selection.end).toFormat('yyyy') : '';
1158
1278
  return {
1159
1279
  textbox: `${start}–${end}, ${year}`,
1160
1280
  textbox2: null
@@ -1164,17 +1284,19 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1164
1284
  return { textbox: null, textbox2: null };
1165
1285
  }
1166
1286
  parseTextboxValues(value, parseFormats, opts) {
1167
- // Only one textbox is used in this mode
1168
- const date = this.parseString(value.textbox, parseFormats, opts);
1287
+ // Only one textbox is used in this mode. Try the quarter-range display format first, then fall back
1288
+ // to the standard single-date formats a user might type (e.g. "Feb 2023").
1289
+ const date = this.parseString(value.textbox, [...this.rangeParseFormats, ...parseFormats], opts);
1169
1290
  if (!date) {
1170
1291
  return null;
1171
1292
  }
1172
1293
  return this.getSelectionFromDate(date);
1173
1294
  }
1174
1295
  getSelectionFromDate(date) {
1296
+ const dt = DateTime.fromJSDate(date);
1175
1297
  return {
1176
- start: moment(date).startOf('quarter').toDate(),
1177
- end: moment(date).endOf('quarter').toDate()
1298
+ start: dt.startOf('quarter').toJSDate(),
1299
+ end: dt.endOf('quarter').toJSDate()
1178
1300
  };
1179
1301
  }
1180
1302
  getNewSelectionFromExisting(previousSelection) {
@@ -1194,8 +1316,8 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1194
1316
  getNextSelection(selection) {
1195
1317
  // Move the selection to the next quarter
1196
1318
  if (DateInput.isSelectionRange(selection) && selection.start) {
1197
- const date = moment(selection.start).add(1, 'quarter');
1198
- return this.getSelectionFromDate(date.toDate());
1319
+ const date = DateTime.fromJSDate(selection.start).plus({ quarters: 1 });
1320
+ return this.getSelectionFromDate(date.toJSDate());
1199
1321
  }
1200
1322
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
1201
1323
  // If it does, just return the selection as-is.
@@ -1204,8 +1326,8 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1204
1326
  getPreviousSelection(selection) {
1205
1327
  // Move the selection to the previous quarter
1206
1328
  if (DateInput.isSelectionRange(selection) && selection.start) {
1207
- const date = moment(selection.start).subtract(1, 'quarter');
1208
- return this.getSelectionFromDate(date.toDate());
1329
+ const date = DateTime.fromJSDate(selection.start).minus({ quarters: 1 });
1330
+ return this.getSelectionFromDate(date.toJSDate());
1209
1331
  }
1210
1332
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
1211
1333
  // If it does, just return the selection as-is.
@@ -1228,13 +1350,13 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1228
1350
  if (!selection) {
1229
1351
  return { textbox: null, textbox2: null };
1230
1352
  }
1231
- // We use the 'll' moment medium format here to make sure the formatted date is formatted according to the user's locale
1353
+ // We use Luxon's DATE_MED preset here (equivalent to moment's 'll') so the formatted date follows the user's locale
1232
1354
  if (DateInput.isSelectionSingleDate(selection)) {
1233
- return { textbox: moment(selection).format('ll'), textbox2: null };
1355
+ return { textbox: DateTime.fromJSDate(selection).toLocaleString(DateTime.DATE_MED), textbox2: null };
1234
1356
  }
1235
1357
  else {
1236
- const start = selection.start ? moment(selection.start).format('ll') : null;
1237
- const end = selection.end ? moment(selection.end).format('ll') : null;
1358
+ const start = selection.start ? DateTime.fromJSDate(selection.start).toLocaleString(DateTime.DATE_MED) : null;
1359
+ const end = selection.end ? DateTime.fromJSDate(selection.end).toLocaleString(DateTime.DATE_MED) : null;
1238
1360
  return { textbox: start, textbox2: end };
1239
1361
  }
1240
1362
  }
@@ -1245,7 +1367,7 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1245
1367
  // because we don't have a fixed range to automatically adjust the dates to. If the user is changing the begin date or end date and ends
1246
1368
  // up crossing the other date, we should clear out the date that's not being changed so that the user can enter a valid range using the
1247
1369
  // begin/end date they just entered.
1248
- if (start && end && moment(start).isAfter(end)) {
1370
+ if (start && end && DateTime.fromJSDate(start) > DateTime.fromJSDate(end)) {
1249
1371
  if (opts?.parseFromEnd) {
1250
1372
  start = null;
1251
1373
  }
@@ -1263,18 +1385,19 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1263
1385
  getSelectionFromDate(date, existingSelection) {
1264
1386
  if (DateInput.isSelectionRange(existingSelection)) {
1265
1387
  const { start, end } = existingSelection;
1388
+ const dDt = DateTime.fromJSDate(date);
1266
1389
  if (start && !end) {
1267
1390
  // If the selected date is before the existing start date, set it as the new start date and use the existing start date as the end date
1268
- return moment(date).isBefore(start) ? { start: date, end: start } : { start, end: date };
1391
+ return dDt < DateTime.fromJSDate(start) ? { start: date, end: start } : { start, end: date };
1269
1392
  }
1270
1393
  if (end && !start) {
1271
1394
  // If the selected date is after the existing end date, set it as the new end date and use the existing end date as the start date
1272
- return moment(date).isBefore(end) ? { start: date, end } : { start: end, end: date };
1395
+ return dDt < DateTime.fromJSDate(end) ? { start: date, end } : { start: end, end: date };
1273
1396
  }
1274
1397
  }
1275
1398
  else if (DateInput.isSelectionSingleDate(existingSelection)) {
1276
1399
  // If the selected date is before the existing date, set it as the new start date and use the existing date as the end date
1277
- return moment(date).isBefore(existingSelection) ? { start: date, end: existingSelection } : { start: existingSelection, end: date };
1400
+ return DateTime.fromJSDate(date) < DateTime.fromJSDate(existingSelection) ? { start: date, end: existingSelection } : { start: existingSelection, end: date };
1278
1401
  }
1279
1402
  // If no selection exists or we already have a complete selection, start a new one with the selected date as the start date.
1280
1403
  return { start: date, end: null };
@@ -1295,9 +1418,9 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1295
1418
  // If we have a valid range, we'll shift the range to the date provided, using it as the end date
1296
1419
  // The new selection will have the same length as the previous.
1297
1420
  if (DateInput.isSelectionRange(existingSelection) && existingSelection.start && existingSelection.end) {
1298
- const length = moment(existingSelection.end).diff(existingSelection.start, 'days');
1421
+ const length = Math.floor(DateTime.fromJSDate(existingSelection.end).diff(DateTime.fromJSDate(existingSelection.start), 'days').days);
1299
1422
  return {
1300
- start: moment(date).subtract(length, 'days').toDate(),
1423
+ start: DateTime.fromJSDate(date).minus({ days: length }).toJSDate(),
1301
1424
  end: date
1302
1425
  };
1303
1426
  }
@@ -1309,10 +1432,11 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1309
1432
  // We can only determine the next selection if we have a valid range
1310
1433
  if (DateInput.isSelectionRange(selection) && selection.start && selection.end) {
1311
1434
  // Shift the range forward by the number of days between the start and end dates
1312
- const length = moment(selection.end).diff(selection.start, 'days');
1435
+ const end = DateTime.fromJSDate(selection.end);
1436
+ const length = Math.floor(end.diff(DateTime.fromJSDate(selection.start), 'days').days);
1313
1437
  return {
1314
- start: moment(selection.end).add(1, 'days').toDate(),
1315
- end: moment(selection.end).add(1 + length, 'days').toDate()
1438
+ start: end.plus({ days: 1 }).toJSDate(),
1439
+ end: end.plus({ days: 1 + length }).toJSDate()
1316
1440
  };
1317
1441
  }
1318
1442
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
@@ -1323,10 +1447,11 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1323
1447
  // We can only determine the previous selection if we have a valid range
1324
1448
  if (DateInput.isSelectionRange(selection) && selection.start && selection.end) {
1325
1449
  // Shift the range back by the number of days between the start and end dates
1326
- const length = moment(selection.end).diff(selection.start, 'days');
1450
+ const start = DateTime.fromJSDate(selection.start);
1451
+ const length = Math.floor(DateTime.fromJSDate(selection.end).diff(start, 'days').days);
1327
1452
  return {
1328
- start: moment(selection.start).subtract(1 + length, 'days').toDate(),
1329
- end: moment(selection.start).subtract(1, 'days').toDate()
1453
+ start: start.minus({ days: 1 + length }).toJSDate(),
1454
+ end: start.minus({ days: 1 }).toJSDate()
1330
1455
  };
1331
1456
  }
1332
1457
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
@@ -1336,31 +1461,33 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1336
1461
  getPrimaryCalendarView(selection, minDate, maxDate, currentView) {
1337
1462
  let viewDate;
1338
1463
  if (DateInput.isSelectionRange(selection)) {
1339
- let start = selection.start ? moment(selection.start) : null;
1340
- let end = selection.end ? moment(selection.end) : null;
1464
+ const start = selection.start ? DateTime.fromJSDate(selection.start) : null;
1465
+ const end = selection.end ? DateTime.fromJSDate(selection.end) : null;
1466
+ const minDt = DateTime.fromJSDate(minDate);
1467
+ const maxDt = DateTime.fromJSDate(maxDate);
1341
1468
  // Since we're in range mode, the primary calendar is mostly concerned with the start date
1342
1469
  if (start) {
1343
1470
  // If we have a start date in the selection and it's before the min date, shift the view to the min date.
1344
- if (start.isBefore(minDate, 'month')) {
1471
+ if (start.startOf('month') < minDt.startOf('month')) {
1345
1472
  viewDate = minDate;
1346
1473
  // If the start is after the max date, shift the view to the max date.
1347
1474
  }
1348
- else if (start.isSameOrAfter(maxDate, 'month')) {
1475
+ else if (start.startOf('month') >= maxDt.startOf('month')) {
1349
1476
  // the secondary calendar will show the max date month.
1350
1477
  // The primary calendar will show the month before the max date.
1351
- viewDate = moment(maxDate).subtract(1, 'month').toDate();
1478
+ viewDate = maxDt.minus({ months: 1 }).toJSDate();
1352
1479
  }
1353
1480
  else {
1354
- viewDate = start.toDate();
1481
+ viewDate = start.toJSDate();
1355
1482
  }
1356
1483
  }
1357
- else if (end && end.isSameOrBefore(minDate, 'month')) {
1484
+ else if (end && end.startOf('month') <= minDt.startOf('month')) {
1358
1485
  // If we don't have a start date, and the end date is the same month or before the min date, shift the view to the min date.
1359
1486
  viewDate = minDate;
1360
1487
  }
1361
- else if (end && end.isSameOrBefore(new Date(), 'month')) {
1488
+ else if (end && end.startOf('month') <= DateTime.now().startOf('month')) {
1362
1489
  // If we don't have a start date, and the end date is the same month or before the current month, shift the view to the month before the end
1363
- viewDate = moment(end).subtract(1, 'month').toDate();
1490
+ viewDate = end.minus({ months: 1 }).toJSDate();
1364
1491
  }
1365
1492
  else {
1366
1493
  // Otherwise, show the current month.
@@ -1378,32 +1505,34 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1378
1505
  getSecondaryCalendarView(selection, minDate, maxDate, currentView) {
1379
1506
  let viewDate;
1380
1507
  if (DateInput.isSelectionRange(selection)) {
1381
- let start = selection.start ? moment(selection.start) : null;
1382
- let end = selection.end ? moment(selection.end) : null;
1508
+ const start = selection.start ? DateTime.fromJSDate(selection.start) : null;
1509
+ const end = selection.end ? DateTime.fromJSDate(selection.end) : null;
1510
+ const minDt = DateTime.fromJSDate(minDate);
1511
+ const maxDt = DateTime.fromJSDate(maxDate);
1383
1512
  // The secondary calendar is primarily concerned with the end date in the selection.
1384
1513
  if (end) {
1385
- if (end.isAfter(maxDate, 'month')) {
1514
+ if (end.startOf('month') > maxDt.startOf('month')) {
1386
1515
  // If the end date is after the max date, shift the view to the max date.
1387
1516
  viewDate = maxDate;
1388
1517
  }
1389
- else if (end.isSameOrBefore(minDate, 'month')) {
1518
+ else if (end.startOf('month') <= minDt.startOf('month')) {
1390
1519
  // If the end date is the same month or before the min date, shift the view to show
1391
1520
  // the month after the min date. The primary calendar will show the min date month.
1392
- viewDate = moment(minDate).add(1, 'month').toDate();
1521
+ viewDate = minDt.plus({ months: 1 }).toJSDate();
1393
1522
  }
1394
1523
  else {
1395
1524
  // Otherwise, show the end date month.
1396
- viewDate = end.toDate();
1525
+ viewDate = end.toJSDate();
1397
1526
  }
1398
1527
  }
1399
- else if (start && start.isSameOrAfter(maxDate, 'month')) {
1528
+ else if (start && start.startOf('month') >= maxDt.startOf('month')) {
1400
1529
  // If we don't have an end date, and the start date is after the max date, show the max date month
1401
1530
  viewDate = maxDate;
1402
1531
  }
1403
- else if (start && start.isSameOrAfter(new Date(), 'month')) {
1532
+ else if (start && start.startOf('month') >= DateTime.now().startOf('month')) {
1404
1533
  // If we don't have an end date, and the start date is on or after the current month,
1405
1534
  // show the month after the start date.
1406
- viewDate = moment(start).add(1, 'month').toDate();
1535
+ viewDate = start.plus({ months: 1 }).toJSDate();
1407
1536
  }
1408
1537
  else {
1409
1538
  // Otherwise, show the current month.
@@ -1438,12 +1567,12 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1438
1567
  // Only one date is needed for formatting since the ranges span a single year.
1439
1568
  // There shouldn't be any case where there's only a single date but it's covered anyway.
1440
1569
  const date = DateInput.isSelectionRange(selection) ? selection.start : selection;
1441
- return { textbox: moment(date).format('YYYY'), textbox2: null };
1570
+ return { textbox: date ? DateTime.fromJSDate(date).toFormat('yyyy') : null, textbox2: null };
1442
1571
  }
1443
1572
  parseString(value, parseFormats, opts) {
1444
- // Including 'YY' and 'YYYY' as valid formats for year input. These aren't included in the default parse
1573
+ // Including 'yy' and 'yyyy' as valid formats for year input. These aren't included in the default parse
1445
1574
  // formats because they cause issues with the default date parsing.
1446
- return super.parseString(value, ['YY', 'YYYY', ...parseFormats], opts);
1575
+ return super.parseString(value, ['yy', 'yyyy', ...parseFormats], opts);
1447
1576
  }
1448
1577
  parseTextboxValues(value, parseFormats, opts) {
1449
1578
  // Only a single textbox is used in year mode
@@ -1454,9 +1583,10 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1454
1583
  return this.getSelectionFromDate(date);
1455
1584
  }
1456
1585
  getSelectionFromDate(date) {
1586
+ const dt = DateTime.fromJSDate(date);
1457
1587
  return {
1458
- start: moment(date).startOf('year').toDate(),
1459
- end: moment(date).endOf('year').toDate()
1588
+ start: dt.startOf('year').toJSDate(),
1589
+ end: dt.endOf('year').toJSDate()
1460
1590
  };
1461
1591
  }
1462
1592
  getNewSelectionFromExisting(previousSelection) {
@@ -1479,8 +1609,8 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1479
1609
  getNextSelection(selection) {
1480
1610
  // Move the selection to the next year
1481
1611
  if (DateInput.isSelectionRange(selection) && selection.start) {
1482
- const date = moment(selection.start).add(1, 'year');
1483
- return this.getSelectionFromDate(date.toDate());
1612
+ const date = DateTime.fromJSDate(selection.start).plus({ years: 1 });
1613
+ return this.getSelectionFromDate(date.toJSDate());
1484
1614
  }
1485
1615
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
1486
1616
  // If it does, just return the selection as-is.
@@ -1489,8 +1619,8 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1489
1619
  getPreviousSelection(selection) {
1490
1620
  // Move the selection to the previous year
1491
1621
  if (DateInput.isSelectionRange(selection) && selection.start) {
1492
- const date = moment(selection.start).subtract(1, 'year');
1493
- return this.getSelectionFromDate(date.toDate());
1622
+ const date = DateTime.fromJSDate(selection.start).minus({ years: 1 });
1623
+ return this.getSelectionFromDate(date.toJSDate());
1494
1624
  }
1495
1625
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
1496
1626
  // If it does, just return the selection as-is.
@@ -1529,10 +1659,10 @@ class DateDisplayPipe {
1529
1659
  transform(date, showEndOfTime, showTime, showSeconds) {
1530
1660
  let display = '';
1531
1661
  let formatString = '';
1532
- if (date && moment(date).isValid() && (showEndOfTime || !DateTimeHelper.isEndOfTime(date))) {
1662
+ if (date != null && DateTimeHelper.toDateTime(date).isValid && (showEndOfTime || !DateTimeHelper.isEndOfTime(date))) {
1533
1663
  // use user-preferred formats
1534
1664
  this.userPreferenceService.getPreferences().subscribe(result => {
1535
- //if preferences exist then format date to users preference
1665
+ //if preferences exist then format date to users preference
1536
1666
  //otherwise use the last user preference
1537
1667
  if (result.preference) {
1538
1668
  formatString = result.preference.dateFormat;
@@ -1546,7 +1676,7 @@ class DateDisplayPipe {
1546
1676
  this.lastFormatString = formatString;
1547
1677
  }
1548
1678
  });
1549
- display = moment(date).format(this.lastFormatString);
1679
+ display = DateTimeHelper.formatDate(date, this.lastFormatString);
1550
1680
  }
1551
1681
  return display;
1552
1682
  }
@@ -1571,6 +1701,32 @@ var DateInput;
1571
1701
  return !!selection && !isSelectionSingleDate(selection);
1572
1702
  }
1573
1703
  DateInput.isSelectionRange = isSelectionRange;
1704
+ /**
1705
+ * Coerces a selection into one backed by real {@link Date} objects.
1706
+ *
1707
+ * The form model is typed as `Date | Range`, but hosts commonly bind ISO date strings
1708
+ * (e.g. values straight off an API response). The display path tolerates strings via
1709
+ * Luxon parsing, but the calendar view builders pass the value to `DateTime.fromJSDate`,
1710
+ * which only accepts a `Date` — a string yields an Invalid Date and crashes the calendar
1711
+ * (`RangeError: Invalid time value`). Normalizing at the boundary lets all downstream
1712
+ * calendar/stepper logic rely on actual Dates. Invalid/unparseable values become null.
1713
+ */
1714
+ function normalizeSelection(selection) {
1715
+ if (selection === null || selection === undefined) {
1716
+ return null;
1717
+ }
1718
+ if (isSelectionSingleDate(selection)) {
1719
+ const dt = DateTimeHelper.toDateTime(selection);
1720
+ return dt.isValid ? dt.toJSDate() : null;
1721
+ }
1722
+ const start = selection.start ? DateTimeHelper.toDateTime(selection.start) : null;
1723
+ const end = selection.end ? DateTimeHelper.toDateTime(selection.end) : null;
1724
+ return {
1725
+ start: start?.isValid ? start.toJSDate() : null,
1726
+ end: end?.isValid ? end.toJSDate() : null
1727
+ };
1728
+ }
1729
+ DateInput.normalizeSelection = normalizeSelection;
1574
1730
  class SelectionStrategies {
1575
1731
  constructor(dateDisplayPipe) {
1576
1732
  this.dateDisplayPipe = dateDisplayPipe;
@@ -1613,9 +1769,14 @@ class CalendarItemComponent {
1613
1769
  this.today = false;
1614
1770
  this.outsideActiveMonth = false;
1615
1771
  }
1772
+ else if (this.item?.date) {
1773
+ const itemDt = DateTime.fromJSDate(this.item.date);
1774
+ this.today = itemDt.hasSame(DateTime.now(), 'day');
1775
+ this.outsideActiveMonth = !itemDt.hasSame(DateTime.fromJSDate(this.view.date), 'month');
1776
+ }
1616
1777
  else {
1617
- this.today = moment(this.item?.date).isSame(moment(), 'day');
1618
- this.outsideActiveMonth = !moment(this.item?.date).isSame(this.view.date, 'month');
1778
+ this.today = false;
1779
+ this.outsideActiveMonth = false;
1619
1780
  }
1620
1781
  if (this.item?.date) {
1621
1782
  this.hidden = this.isHidden(this.item.date, this.minDate, this.maxDate, this.view.mode);
@@ -1629,24 +1790,30 @@ class CalendarItemComponent {
1629
1790
  if (!date || !selection) {
1630
1791
  return false;
1631
1792
  }
1793
+ const unit = view ?? 'day';
1794
+ const dt = DateTime.fromJSDate(date);
1632
1795
  if (DateInput.isSelectionSingleDate(selection)) {
1633
- return moment(date).isSame(selection, view);
1796
+ return dt.hasSame(DateTime.fromJSDate(selection), unit);
1634
1797
  }
1635
1798
  else {
1636
1799
  if (selection.start && !selection.end) {
1637
- return moment(date).isSame(selection.start, view);
1800
+ return dt.hasSame(DateTime.fromJSDate(selection.start), unit);
1638
1801
  }
1639
1802
  else if (!selection.start && selection.end) {
1640
- return moment(date).isSame(selection.end, view);
1803
+ return dt.hasSame(DateTime.fromJSDate(selection.end), unit);
1641
1804
  }
1642
1805
  else if (selection.start && selection.end) {
1643
- return moment(date).isBetween(selection.start, selection.end, view, '[]');
1806
+ const d = dt.startOf(unit);
1807
+ const s = DateTime.fromJSDate(selection.start).startOf(unit);
1808
+ const e = DateTime.fromJSDate(selection.end).startOf(unit);
1809
+ return d >= s && d <= e;
1644
1810
  }
1645
1811
  }
1646
1812
  return false;
1647
1813
  }
1648
1814
  isHidden(date, minDate, maxDate, view) {
1649
- return moment(date).isBefore(minDate, view) || moment(date).isAfter(maxDate, view);
1815
+ const d = DateTime.fromJSDate(date).startOf(view);
1816
+ return d < DateTime.fromJSDate(minDate).startOf(view) || d > DateTime.fromJSDate(maxDate).startOf(view);
1650
1817
  }
1651
1818
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CalendarItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1652
1819
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.4", type: CalendarItemComponent, isStandalone: false, selector: "button[ec-calendar-item]", inputs: { item: "item", selection: "selection", view: "view", minDate: "minDate", maxDate: "maxDate" }, host: { properties: { "class.is-today": "this.today", "class.is-selected": "this.selected", "class.is-outside-active-month": "this.outsideActiveMonth", "class.is-hidden": "this.hidden", "attr.disabled": "this.disabled" } }, usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{font-size:var(--ec-font-size-action);height:2rem;line-height:1.25rem;padding:.3125rem .5rem;border:0;border-radius:var(--ec-border-radius);display:flex;align-items:center;justify-content:center;cursor:pointer}:host .label{display:flex;align-items:center;justify-content:center;white-space:nowrap;flex:auto}:host .ec-icon{flex:none}:host .ec-icon+.label{flex:none;margin-left:.25rem}:host.has-badge{padding-right:.0625rem}:host:focus{outline:none;position:relative;z-index:1}:host:disabled{background-color:var(--ec-background-color-disabled);border:1px solid var(--ec-form-control-border-color-disabled);color:var(--ec-color-disabled-dark);opacity:var(--ec-form-control-opacity-disabled);cursor:default}:host:disabled .ec-icon{color:var(--ec-color-disabled-dark)}:host{background-color:transparent}:host:disabled{background-color:transparent;border-color:transparent;color:var(--ec-color-hint-dark);--ec-color-icon: var(--ec-color-hint-dark)}:host:hover:not(:disabled){background-color:var(--ec-background-color-hover)}:host:active:not(:disabled){background-color:var(--ec-background-color-selected);font-weight:700}:host:focus:not(:disabled){box-shadow:var(--ec-button-box-shadow-active, 0 0 0 2px var(--ec-border-color-focus))}:host.is-selected{background-color:var(--ec-background-color-selected);font-weight:700}:host{line-height:1.125rem}:host.is-selected,:host.is-selected:disabled{background-color:var(--ec-background-color-selected);font-weight:700}:host.is-today{position:relative}:host.is-today:after{content:\"\";position:absolute;top:50%;left:50%;width:1.75rem;height:1.75rem;transform:translate(-50%,-50%);border:1px solid;border-radius:50%;pointer-events:none}:host.is-outside-active-month{color:var(--ec-color-hint-dark)}:host.is-hidden{opacity:0!important}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -1695,8 +1862,12 @@ class CalendarComponent {
1695
1862
  this.viewChange = new EventEmitter();
1696
1863
  /** Array of calendar item rows to display in the calendar. */
1697
1864
  this.calendarItems = [];
1698
- /** Weekday labels to show at the top of the calendar in day view. */
1699
- this.weekDays = moment.weekdaysShort().map(d => d.slice(0, 1));
1865
+ /** Weekday labels to show at the top of the calendar in day view. Starts on Sunday to match the calendar grid layout. */
1866
+ this.weekDays = (() => {
1867
+ // Info.weekdays returns Monday-first; reorder so Sunday is first to match the calendar grid.
1868
+ const mondayFirst = Info.weekdays('short');
1869
+ return [mondayFirst[6], ...mondayFirst.slice(0, 6)].map(d => d.slice(0, 1));
1870
+ })();
1700
1871
  this.disablePreviousButton = false;
1701
1872
  this.disableNextButton = false;
1702
1873
  // Trackby functions for ngFor loops. These reduce the number of DOM elements that need to be updated when the calendar is re-drawn.
@@ -1720,19 +1891,19 @@ class CalendarComponent {
1720
1891
  onNextClick() {
1721
1892
  // If we're in day view, we're incrementing by month
1722
1893
  // In month, quarter, and year views we're incrementing by year\
1723
- const unit = this.view.mode === 'day' ? 'month' : 'year';
1894
+ const unit = this.view.mode === 'day' ? 'months' : 'years';
1724
1895
  // In year view, we're moving by 16 years at a time. Otherwise we're just moving by 1 month/year.
1725
1896
  const count = this.view.mode === 'year' ? 16 : 1;
1726
- const goToDate = moment(this.view.date).add(count, unit).toDate();
1897
+ const goToDate = DateTime.fromJSDate(this.view.date).plus({ [unit]: count }).toJSDate();
1727
1898
  this.drawCalendar({ mode: this.view.mode, date: goToDate });
1728
1899
  }
1729
1900
  onPreviousClick() {
1730
1901
  // If we're in day view, we're incrementing by month
1731
1902
  // In month, quarter, and year views we're incrementing by year
1732
- const unit = this.view.mode === 'day' ? 'month' : 'year';
1903
+ const unit = this.view.mode === 'day' ? 'months' : 'years';
1733
1904
  // In year view, we're moving by 16 years at a time. Otherwise we're just moving by 1 month/year.
1734
1905
  const count = this.view.mode === 'year' ? 16 : 1;
1735
- const goToDate = moment(this.view.date).subtract(count, unit).toDate();
1906
+ const goToDate = DateTime.fromJSDate(this.view.date).minus({ [unit]: count }).toJSDate();
1736
1907
  this.drawCalendar({ mode: this.view.mode, date: goToDate });
1737
1908
  }
1738
1909
  /** Switches the calendar to month view. */
@@ -1799,13 +1970,14 @@ class CalendarComponent {
1799
1970
  this.view = view;
1800
1971
  this.viewChange.emit(view);
1801
1972
  }
1802
- this.month = moment(this.view.date).format('MMM');
1803
- this.year = moment(this.view.date).format('YYYY');
1973
+ const viewDt = DateTime.fromJSDate(this.view.date);
1974
+ this.month = viewDt.toFormat('MMM');
1975
+ this.year = viewDt.toFormat('yyyy');
1804
1976
  this.calendarItems = this.getCalendarItems(this.view);
1805
1977
  const startYear = this.calendarItems[0][0].date < this.minDate ? this.minDate : this.calendarItems[0][0].date;
1806
1978
  const lastItem = this.getLastItem();
1807
1979
  const endYear = lastItem.date > this.maxDate ? this.maxDate : lastItem.date;
1808
- this.yearRange = `${moment(startYear).format('YYYY')}&ndash;${moment(endYear).format('YYYY')}`;
1980
+ this.yearRange = `${DateTime.fromJSDate(startYear).toFormat('yyyy')}&ndash;${DateTime.fromJSDate(endYear).toFormat('yyyy')}`;
1809
1981
  this.updateNextPreviousStates();
1810
1982
  }
1811
1983
  getCalendarItems(view) {
@@ -1817,53 +1989,58 @@ class CalendarComponent {
1817
1989
  }
1818
1990
  }
1819
1991
  getDayViewItems(activeDate) {
1820
- // 6 rows of 7 days = 42 days
1821
- return range(0, 6).map(r => {
1822
- return range(0, 7).map(d => {
1823
- // Use the start of the week of the first day of the month.
1824
- // This pads out the first week with any days from the previous month.
1825
- const date = moment(activeDate).startOf('month').startOf('week').add(r * 7 + d, 'day');
1992
+ // 6 rows of 7 days = 42 days.
1993
+ // Use the start of the week of the first day of the month, anchored to Sunday to match the calendar grid.
1994
+ // Luxon's startOf('week') is Monday-based per ISO, so shift back to Sunday explicitly.
1995
+ const monthStart = DateTime.fromJSDate(activeDate).startOf('month');
1996
+ const sundayStart = monthStart.weekday === 7 ? monthStart : monthStart.startOf('week').minus({ days: 1 });
1997
+ return range(0, 6).map((r) => {
1998
+ return range(0, 7).map((d) => {
1999
+ const date = sundayStart.plus({ days: r * 7 + d });
1826
2000
  return {
1827
- date: date.toDate(),
1828
- label: date.format('D')
2001
+ date: date.toJSDate(),
2002
+ label: date.toFormat('d')
1829
2003
  };
1830
2004
  });
1831
2005
  });
1832
2006
  }
1833
2007
  getMonthViewItems(activeDate) {
1834
2008
  // 4 rows of 3 months = 12 months
1835
- return range(0, 4).map(r => {
1836
- return range(0, 3).map(m => {
1837
- const date = moment(activeDate).startOf('year').add(r * 3 + m, 'month');
2009
+ const yearStart = DateTime.fromJSDate(activeDate).startOf('year');
2010
+ return range(0, 4).map((r) => {
2011
+ return range(0, 3).map((m) => {
2012
+ const date = yearStart.plus({ months: r * 3 + m });
1838
2013
  return {
1839
- date: date.toDate(),
1840
- label: date.format('MMM')
2014
+ date: date.toJSDate(),
2015
+ label: date.toFormat('MMM')
1841
2016
  };
1842
2017
  });
1843
2018
  });
1844
2019
  }
1845
2020
  getQuarterViewItems(activeDate) {
1846
2021
  // 2 rows of 2 quarters = 4 quarters
1847
- return range(0, 2).map(r => {
1848
- return range(0, 2).map(q => {
1849
- const date = moment(activeDate).startOf('year').add(r * 2 + q, 'quarter');
1850
- const endDate = moment(date).endOf('quarter');
2022
+ const yearStart = DateTime.fromJSDate(activeDate).startOf('year');
2023
+ return range(0, 2).map((r) => {
2024
+ return range(0, 2).map((q) => {
2025
+ const date = yearStart.plus({ quarters: r * 2 + q });
2026
+ const endDate = date.endOf('quarter');
1851
2027
  return {
1852
- date: date.toDate(),
1853
- label: `${date.format('MMM')}&ndash;${endDate.format('MMM')}`
2028
+ date: date.toJSDate(),
2029
+ label: `${date.toFormat('MMM')}&ndash;${endDate.toFormat('MMM')}`
1854
2030
  };
1855
2031
  });
1856
2032
  });
1857
2033
  }
1858
2034
  getYearViewItems(activeDate) {
1859
2035
  // 4 rows of 4 years = 16 years
1860
- return range(0, 4).map(r => {
1861
- return range(0, 4).map(y => {
1862
- // Subtracting 8 years to put the current active year at the beginning of the 3rd row.
1863
- const date = moment(activeDate).startOf('year').subtract(8, 'years').add(r * 4 + y, 'year');
2036
+ // Subtracting 8 years to put the current active year at the beginning of the 3rd row.
2037
+ const yearStart = DateTime.fromJSDate(activeDate).startOf('year').minus({ years: 8 });
2038
+ return range(0, 4).map((r) => {
2039
+ return range(0, 4).map((y) => {
2040
+ const date = yearStart.plus({ years: r * 4 + y });
1864
2041
  return {
1865
- date: date.toDate(),
1866
- label: date.format('YYYY')
2042
+ date: date.toJSDate(),
2043
+ label: date.toFormat('yyyy')
1867
2044
  };
1868
2045
  });
1869
2046
  });
@@ -1873,24 +2050,20 @@ class CalendarComponent {
1873
2050
  // We don't want to disable the next/previous buttons if the min or max date is within the
1874
2051
  // next or previous view
1875
2052
  const unit = this.view.mode === 'day' ? 'month' : 'year';
2053
+ const pluralUnit = this.view.mode === 'day' ? 'months' : 'years';
2054
+ const viewDt = DateTime.fromJSDate(this.view.date);
1876
2055
  // When in year view, we need to subtract 9 years to determine if we're past the min date.
1877
2056
  // The year view is 16 years long, and the current date is the 9th year in the view.
1878
2057
  const subCount = this.view.mode === 'year' ? 9 : 1;
1879
- if (moment(this.view.date).subtract(subCount, unit).isBefore(this.minDate, unit)) {
1880
- this.disablePreviousButton = true;
1881
- }
1882
- else {
1883
- this.disablePreviousButton = false;
1884
- }
2058
+ const prev = viewDt.minus({ [pluralUnit]: subCount }).startOf(unit);
2059
+ const minDt = DateTime.fromJSDate(this.minDate).startOf(unit);
2060
+ this.disablePreviousButton = prev < minDt;
1885
2061
  // When in year view, we need to add 8 years to determine if we're past the max date.
1886
2062
  // The year view is 16 years long, and the current date is the 9th year in the view.
1887
2063
  const addCount = this.view.mode === 'year' ? 8 : 1;
1888
- if (moment(this.view.date).add(addCount, unit).isAfter(this.maxDate, unit)) {
1889
- this.disableNextButton = true;
1890
- }
1891
- else {
1892
- this.disableNextButton = false;
1893
- }
2064
+ const next = viewDt.plus({ [pluralUnit]: addCount }).startOf(unit);
2065
+ const maxDt = DateTime.fromJSDate(this.maxDate).startOf(unit);
2066
+ this.disableNextButton = next > maxDt;
1894
2067
  }
1895
2068
  getLastItem() {
1896
2069
  const lastRow = this.calendarItems[this.calendarItems.length - 1];
@@ -2325,11 +2498,11 @@ class TooltipComponent {
2325
2498
  this.overlayRef?.dispose();
2326
2499
  }
2327
2500
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2328
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: TooltipComponent, isStandalone: false, selector: "ec-tooltip", host: { properties: { "style.--ec-tooltip-background-color": "this.backgroundColor", "style.--ec-tooltip-color-title": "this.titleColor" } }, ngImport: i0, template: "<article id=\"{{id}}\" class=\"py-3 position-{{position}}\" [class.show-arrow]=\"!hideArrow\">\n @if (title || subtitle) {\n <header class=\"mb-3\">\n <h1 class=\"text-heading-2 px-3 mb-0\">{{title}}</h1>\n <p class=\"text-caption-1 px-3 mb-0\">{{subtitle}}</p>\n </header>\n }\n\n @if (customContent) {\n <ng-container *ngTemplateOutlet=\"customContent\"></ng-container>\n } @else {\n <div class=\"text-body-1 px-3\" [innerHTML]=\"text\"></div>\n }\n\n\n @if (dismissible) {\n <ec-button id=\"tooltipDismiss\" type=\"icon\" icon=\"icon-cancel\" ariaLabel=\"Tooltip_DismissButton_SC\" (clicked)=\"hide()\"></ec-button>\n }\n</article>\n", styles: [":host{display:block;padding:.75rem}article{background-color:var(--ec-tooltip-background-color, #162F3B);color:var(--ec-tooltip-color, var(--ec-color-primary-light));border-radius:var(--ec-border-radius-card);position:relative;box-shadow:var(--ec-box-shadow-overlay);--ec-color-link: var(--ec-color-link-light)}article.show-arrow:after{content:\"\";height:0;width:0;border:.625rem solid transparent;display:block;position:absolute;pointer-events:none}article.show-arrow.position-top-left:after,article.show-arrow.position-top-center:after,article.show-arrow.position-top-right:after{bottom:-1.25rem;border-top-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-bottom-left:after,article.show-arrow.position-bottom-center:after,article.show-arrow.position-bottom-right:after{top:-1.25rem;border-bottom-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-top-left:after,article.show-arrow.position-bottom-left:after{right:1rem}article.show-arrow.position-top-right:after,article.show-arrow.position-bottom-right:after{left:1rem}article.show-arrow.position-top-center:after,article.show-arrow.position-bottom-center:after{right:50%;transform:translate(50%)}article.show-arrow.position-right-top:after,article.show-arrow.position-right-center:after,article.show-arrow.position-right-bottom:after{left:-1.25rem;border-right-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-left-top:after,article.show-arrow.position-left-center:after,article.show-arrow.position-left-bottom:after{right:-1.25rem;border-left-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-left-top:after,article.show-arrow.position-right-top:after{top:1rem}article.show-arrow.position-left-bottom:after,article.show-arrow.position-right-bottom:after{bottom:1rem}article.show-arrow.position-left-center:after,article.show-arrow.position-right-center:after{bottom:50%;transform:translateY(50%)}.text-heading-2{color:var(--ec-tooltip-color-title, var(--ec-color-primary-light))}.text-body-1{color:var(--ec-color-primary-light)}.text-body-1 ::ng-deep p:last-child{margin-bottom:0}.text-body-1 ::ng-deep a{font-weight:var(--ec-font-weight-bold)}.text-caption-1{color:var(--ec-color-secondary-light)}ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15);position:absolute;top:.25rem;right:.25rem}\n"], dependencies: [{ kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ButtonComponent, selector: "ec-button", inputs: ["id", "disabled", "lockButton", "icon", "iconColor", "label", "ariaLabel", "ariaExpanded", "ariaHaspopup", "ariaActivedescendant", "ariaControls", "badge", "tabindex", "type", "pending", "pendingIcon", "customTemplate", "isSubmit", "autofocus"], outputs: ["clicked"] }] }); }
2501
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: TooltipComponent, isStandalone: false, selector: "ec-tooltip", host: { properties: { "style.--ec-tooltip-background-color": "this.backgroundColor", "style.--ec-tooltip-color-title": "this.titleColor" } }, ngImport: i0, template: "<article id=\"{{id}}\" role=\"tooltip\" class=\"py-3 position-{{position}}\" [class.show-arrow]=\"!hideArrow\">\n @if (title || subtitle) {\n <header class=\"mb-3\">\n <h1 class=\"text-heading-2 px-3 mb-0\">{{title}}</h1>\n <p class=\"text-caption-1 px-3 mb-0\">{{subtitle}}</p>\n </header>\n }\n\n @if (customContent) {\n <ng-container *ngTemplateOutlet=\"customContent\"></ng-container>\n } @else {\n <div class=\"text-body-1 px-3\" [innerHTML]=\"text\"></div>\n }\n\n\n @if (dismissible) {\n <ec-button id=\"tooltipDismiss\" type=\"icon\" icon=\"icon-cancel\" ariaLabel=\"Tooltip_DismissButton_SC\" (clicked)=\"hide()\"></ec-button>\n }\n</article>\n", styles: [":host{display:block;padding:.75rem}article{background-color:var(--ec-tooltip-background-color, #162F3B);color:var(--ec-tooltip-color, var(--ec-color-primary-light));border-radius:var(--ec-border-radius-card);position:relative;box-shadow:var(--ec-box-shadow-overlay);--ec-color-link: var(--ec-color-link-light)}article.show-arrow:after{content:\"\";height:0;width:0;border:.625rem solid transparent;display:block;position:absolute;pointer-events:none}article.show-arrow.position-top-left:after,article.show-arrow.position-top-center:after,article.show-arrow.position-top-right:after{bottom:-1.25rem;border-top-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-bottom-left:after,article.show-arrow.position-bottom-center:after,article.show-arrow.position-bottom-right:after{top:-1.25rem;border-bottom-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-top-left:after,article.show-arrow.position-bottom-left:after{right:1rem}article.show-arrow.position-top-right:after,article.show-arrow.position-bottom-right:after{left:1rem}article.show-arrow.position-top-center:after,article.show-arrow.position-bottom-center:after{right:50%;transform:translate(50%)}article.show-arrow.position-right-top:after,article.show-arrow.position-right-center:after,article.show-arrow.position-right-bottom:after{left:-1.25rem;border-right-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-left-top:after,article.show-arrow.position-left-center:after,article.show-arrow.position-left-bottom:after{right:-1.25rem;border-left-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-left-top:after,article.show-arrow.position-right-top:after{top:1rem}article.show-arrow.position-left-bottom:after,article.show-arrow.position-right-bottom:after{bottom:1rem}article.show-arrow.position-left-center:after,article.show-arrow.position-right-center:after{bottom:50%;transform:translateY(50%)}.text-heading-2{color:var(--ec-tooltip-color-title, var(--ec-color-primary-light))}.text-body-1{color:var(--ec-color-primary-light)}.text-body-1 ::ng-deep p:last-child{margin-bottom:0}.text-body-1 ::ng-deep a{font-weight:var(--ec-font-weight-bold)}.text-caption-1{color:var(--ec-color-secondary-light)}ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15);position:absolute;top:.25rem;right:.25rem}\n"], dependencies: [{ kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ButtonComponent, selector: "ec-button", inputs: ["id", "disabled", "lockButton", "icon", "iconColor", "label", "ariaLabel", "ariaExpanded", "ariaHaspopup", "ariaActivedescendant", "ariaControls", "badge", "tabindex", "type", "pending", "pendingIcon", "customTemplate", "isSubmit", "autofocus"], outputs: ["clicked"] }] }); }
2329
2502
  }
2330
2503
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TooltipComponent, decorators: [{
2331
2504
  type: Component,
2332
- args: [{ selector: 'ec-tooltip', standalone: false, template: "<article id=\"{{id}}\" class=\"py-3 position-{{position}}\" [class.show-arrow]=\"!hideArrow\">\n @if (title || subtitle) {\n <header class=\"mb-3\">\n <h1 class=\"text-heading-2 px-3 mb-0\">{{title}}</h1>\n <p class=\"text-caption-1 px-3 mb-0\">{{subtitle}}</p>\n </header>\n }\n\n @if (customContent) {\n <ng-container *ngTemplateOutlet=\"customContent\"></ng-container>\n } @else {\n <div class=\"text-body-1 px-3\" [innerHTML]=\"text\"></div>\n }\n\n\n @if (dismissible) {\n <ec-button id=\"tooltipDismiss\" type=\"icon\" icon=\"icon-cancel\" ariaLabel=\"Tooltip_DismissButton_SC\" (clicked)=\"hide()\"></ec-button>\n }\n</article>\n", styles: [":host{display:block;padding:.75rem}article{background-color:var(--ec-tooltip-background-color, #162F3B);color:var(--ec-tooltip-color, var(--ec-color-primary-light));border-radius:var(--ec-border-radius-card);position:relative;box-shadow:var(--ec-box-shadow-overlay);--ec-color-link: var(--ec-color-link-light)}article.show-arrow:after{content:\"\";height:0;width:0;border:.625rem solid transparent;display:block;position:absolute;pointer-events:none}article.show-arrow.position-top-left:after,article.show-arrow.position-top-center:after,article.show-arrow.position-top-right:after{bottom:-1.25rem;border-top-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-bottom-left:after,article.show-arrow.position-bottom-center:after,article.show-arrow.position-bottom-right:after{top:-1.25rem;border-bottom-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-top-left:after,article.show-arrow.position-bottom-left:after{right:1rem}article.show-arrow.position-top-right:after,article.show-arrow.position-bottom-right:after{left:1rem}article.show-arrow.position-top-center:after,article.show-arrow.position-bottom-center:after{right:50%;transform:translate(50%)}article.show-arrow.position-right-top:after,article.show-arrow.position-right-center:after,article.show-arrow.position-right-bottom:after{left:-1.25rem;border-right-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-left-top:after,article.show-arrow.position-left-center:after,article.show-arrow.position-left-bottom:after{right:-1.25rem;border-left-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-left-top:after,article.show-arrow.position-right-top:after{top:1rem}article.show-arrow.position-left-bottom:after,article.show-arrow.position-right-bottom:after{bottom:1rem}article.show-arrow.position-left-center:after,article.show-arrow.position-right-center:after{bottom:50%;transform:translateY(50%)}.text-heading-2{color:var(--ec-tooltip-color-title, var(--ec-color-primary-light))}.text-body-1{color:var(--ec-color-primary-light)}.text-body-1 ::ng-deep p:last-child{margin-bottom:0}.text-body-1 ::ng-deep a{font-weight:var(--ec-font-weight-bold)}.text-caption-1{color:var(--ec-color-secondary-light)}ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15);position:absolute;top:.25rem;right:.25rem}\n"] }]
2505
+ args: [{ selector: 'ec-tooltip', standalone: false, template: "<article id=\"{{id}}\" role=\"tooltip\" class=\"py-3 position-{{position}}\" [class.show-arrow]=\"!hideArrow\">\n @if (title || subtitle) {\n <header class=\"mb-3\">\n <h1 class=\"text-heading-2 px-3 mb-0\">{{title}}</h1>\n <p class=\"text-caption-1 px-3 mb-0\">{{subtitle}}</p>\n </header>\n }\n\n @if (customContent) {\n <ng-container *ngTemplateOutlet=\"customContent\"></ng-container>\n } @else {\n <div class=\"text-body-1 px-3\" [innerHTML]=\"text\"></div>\n }\n\n\n @if (dismissible) {\n <ec-button id=\"tooltipDismiss\" type=\"icon\" icon=\"icon-cancel\" ariaLabel=\"Tooltip_DismissButton_SC\" (clicked)=\"hide()\"></ec-button>\n }\n</article>\n", styles: [":host{display:block;padding:.75rem}article{background-color:var(--ec-tooltip-background-color, #162F3B);color:var(--ec-tooltip-color, var(--ec-color-primary-light));border-radius:var(--ec-border-radius-card);position:relative;box-shadow:var(--ec-box-shadow-overlay);--ec-color-link: var(--ec-color-link-light)}article.show-arrow:after{content:\"\";height:0;width:0;border:.625rem solid transparent;display:block;position:absolute;pointer-events:none}article.show-arrow.position-top-left:after,article.show-arrow.position-top-center:after,article.show-arrow.position-top-right:after{bottom:-1.25rem;border-top-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-bottom-left:after,article.show-arrow.position-bottom-center:after,article.show-arrow.position-bottom-right:after{top:-1.25rem;border-bottom-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-top-left:after,article.show-arrow.position-bottom-left:after{right:1rem}article.show-arrow.position-top-right:after,article.show-arrow.position-bottom-right:after{left:1rem}article.show-arrow.position-top-center:after,article.show-arrow.position-bottom-center:after{right:50%;transform:translate(50%)}article.show-arrow.position-right-top:after,article.show-arrow.position-right-center:after,article.show-arrow.position-right-bottom:after{left:-1.25rem;border-right-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-left-top:after,article.show-arrow.position-left-center:after,article.show-arrow.position-left-bottom:after{right:-1.25rem;border-left-color:var(--ec-tooltip-background-color, #162F3B)}article.show-arrow.position-left-top:after,article.show-arrow.position-right-top:after{top:1rem}article.show-arrow.position-left-bottom:after,article.show-arrow.position-right-bottom:after{bottom:1rem}article.show-arrow.position-left-center:after,article.show-arrow.position-right-center:after{bottom:50%;transform:translateY(50%)}.text-heading-2{color:var(--ec-tooltip-color-title, var(--ec-color-primary-light))}.text-body-1{color:var(--ec-color-primary-light)}.text-body-1 ::ng-deep p:last-child{margin-bottom:0}.text-body-1 ::ng-deep a{font-weight:var(--ec-font-weight-bold)}.text-caption-1{color:var(--ec-color-secondary-light)}ec-button{--ec-button-color-icon: var(--ec-color-secondary-light);--ec-button-color-icon-hover: var(--ec-color-primary-light);--ec-background-color-hover: rgba(255,255,255,.15);position:absolute;top:.25rem;right:.25rem}\n"] }]
2333
2506
  }], propDecorators: { backgroundColor: [{
2334
2507
  type: HostBinding,
2335
2508
  args: ['style.--ec-tooltip-background-color']
@@ -4274,11 +4447,11 @@ class ViewOverlayComponent {
4274
4447
  }
4275
4448
  }
4276
4449
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ViewOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4277
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: ViewOverlayComponent, isStandalone: false, selector: "[ecOverlay]", inputs: { status: "status", message: "message", action: "action", noDataTemplate: "noDataTemplate", displayAsMask: "displayAsMask", overlayClassList: "overlayClassList" }, ngImport: i0, template: "<!-- Transcluded Content -->\n@if (displayAsMask || (!displayAsMask && status === 'hasData')) {\n <ng-content></ng-content>\n}\n<!--Used by GI tests to know the overlay status whether we use ngIf or mask version. No visual impact-->\n<span [hidden]=\"true\"\nclass=\"overlay-status-{{status}}\"></span>\n<!-- Overlay goes last so it is rendered on top of preceding content due to source order -->\n@if (status !== 'hasData') {\n <div\n class=\"overlay flex-grow {{overlayClassList}}\"\n\t [ngClass]=\"{'not-mask': !displayAsMask,\n\t\t\t\t'overlay-error': status === 'error',\n\t\t\t\t'overlay-nodata': status === 'noData',\n\t\t\t\t'overlay-pending': status === 'pending'}\">\n <!--Pending Spinner-->\n <ec-spinner [hidden]=\"status !== 'pending'\"></ec-spinner>\n @if (status === 'noData' && noDataTemplate) {\n <ng-container *ngTemplateOutlet=\"noDataTemplate\"></ng-container>\n }\n @if ((status === 'noData' && !noDataTemplate) || status !== 'noData') {\n <!--Status Message-->\n @if (message) {\n <div id=\"statusMessage\"\n class=\"message\"\n [ngClass]=\"{'error': status === 'error', 'mt-1': status === 'pending'}\"\n [innerHtml]=\"message | translate\">\n </div>\n }\n <!-- Action -->\n @if (action?.onClick) {\n <ec-button type=\"common\"\n class=\"mt-3\"\n [icon]=\"action?.icon\"\n (clicked)=\"actionClicked($event)\"\n [label]=\"action?.label\"\n [hidden]=\"status === 'pending'\">\n </ec-button>\n }\n }\n </div>\n}", 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.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ButtonComponent, selector: "ec-button", inputs: ["id", "disabled", "lockButton", "icon", "iconColor", "label", "ariaLabel", "ariaExpanded", "ariaHaspopup", "ariaActivedescendant", "ariaControls", "badge", "tabindex", "type", "pending", "pendingIcon", "customTemplate", "isSubmit", "autofocus"], outputs: ["clicked"] }, { kind: "component", type: SpinnerComponent, selector: "ec-spinner" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] }); }
4450
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: ViewOverlayComponent, isStandalone: false, selector: "[ecOverlay]", inputs: { status: "status", message: "message", action: "action", noDataTemplate: "noDataTemplate", displayAsMask: "displayAsMask", overlayClassList: "overlayClassList" }, ngImport: i0, template: "<!-- Transcluded Content -->\n@if (displayAsMask || (!displayAsMask && status === 'hasData')) {\n <ng-content></ng-content>\n}\n<!--Used by GI tests to know the overlay status whether we use ngIf or mask version. No visual impact-->\n<span [hidden]=\"true\"\nclass=\"overlay-status-{{status}}\"></span>\n<!-- Overlay goes last so it is rendered on top of preceding content due to source order -->\n@if (status !== 'hasData') {\n <div\n class=\"overlay flex-grow {{overlayClassList}}\"\n [attr.role]=\"status === 'error' ? 'alert' : (status === 'pending' ? null : 'status')\"\n [attr.aria-live]=\"status === 'error' ? 'assertive' : (status === 'pending' ? null : 'polite')\"\n [attr.aria-atomic]=\"status === 'pending' ? null : 'true'\"\n\t [ngClass]=\"{'not-mask': !displayAsMask,\n\t\t\t\t'overlay-error': status === 'error',\n\t\t\t\t'overlay-nodata': status === 'noData',\n\t\t\t\t'overlay-pending': status === 'pending'}\">\n <!--Pending Spinner-->\n <ec-spinner [hidden]=\"status !== 'pending'\"></ec-spinner>\n @if (status === 'noData' && noDataTemplate) {\n <ng-container *ngTemplateOutlet=\"noDataTemplate\"></ng-container>\n }\n @if ((status === 'noData' && !noDataTemplate) || status !== 'noData') {\n <!--Status Message-->\n @if (message) {\n <div id=\"statusMessage\"\n class=\"message\"\n [ngClass]=\"{'error': status === 'error', 'mt-1': status === 'pending'}\"\n [innerHtml]=\"message | translate\">\n </div>\n }\n <!-- Action -->\n @if (action?.onClick) {\n <ec-button type=\"common\"\n class=\"mt-3\"\n [icon]=\"action?.icon\"\n (clicked)=\"actionClicked($event)\"\n [label]=\"action?.label\"\n [hidden]=\"status === 'pending'\">\n </ec-button>\n }\n }\n </div>\n}", 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.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ButtonComponent, selector: "ec-button", inputs: ["id", "disabled", "lockButton", "icon", "iconColor", "label", "ariaLabel", "ariaExpanded", "ariaHaspopup", "ariaActivedescendant", "ariaControls", "badge", "tabindex", "type", "pending", "pendingIcon", "customTemplate", "isSubmit", "autofocus"], outputs: ["clicked"] }, { kind: "component", type: SpinnerComponent, selector: "ec-spinner" }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] }); }
4278
4451
  }
4279
4452
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ViewOverlayComponent, decorators: [{
4280
4453
  type: Component,
4281
- args: [{ selector: '[ecOverlay]', standalone: false, template: "<!-- Transcluded Content -->\n@if (displayAsMask || (!displayAsMask && status === 'hasData')) {\n <ng-content></ng-content>\n}\n<!--Used by GI tests to know the overlay status whether we use ngIf or mask version. No visual impact-->\n<span [hidden]=\"true\"\nclass=\"overlay-status-{{status}}\"></span>\n<!-- Overlay goes last so it is rendered on top of preceding content due to source order -->\n@if (status !== 'hasData') {\n <div\n class=\"overlay flex-grow {{overlayClassList}}\"\n\t [ngClass]=\"{'not-mask': !displayAsMask,\n\t\t\t\t'overlay-error': status === 'error',\n\t\t\t\t'overlay-nodata': status === 'noData',\n\t\t\t\t'overlay-pending': status === 'pending'}\">\n <!--Pending Spinner-->\n <ec-spinner [hidden]=\"status !== 'pending'\"></ec-spinner>\n @if (status === 'noData' && noDataTemplate) {\n <ng-container *ngTemplateOutlet=\"noDataTemplate\"></ng-container>\n }\n @if ((status === 'noData' && !noDataTemplate) || status !== 'noData') {\n <!--Status Message-->\n @if (message) {\n <div id=\"statusMessage\"\n class=\"message\"\n [ngClass]=\"{'error': status === 'error', 'mt-1': status === 'pending'}\"\n [innerHtml]=\"message | translate\">\n </div>\n }\n <!-- Action -->\n @if (action?.onClick) {\n <ec-button type=\"common\"\n class=\"mt-3\"\n [icon]=\"action?.icon\"\n (clicked)=\"actionClicked($event)\"\n [label]=\"action?.label\"\n [hidden]=\"status === 'pending'\">\n </ec-button>\n }\n }\n </div>\n}", 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"] }]
4454
+ args: [{ selector: '[ecOverlay]', standalone: false, template: "<!-- Transcluded Content -->\n@if (displayAsMask || (!displayAsMask && status === 'hasData')) {\n <ng-content></ng-content>\n}\n<!--Used by GI tests to know the overlay status whether we use ngIf or mask version. No visual impact-->\n<span [hidden]=\"true\"\nclass=\"overlay-status-{{status}}\"></span>\n<!-- Overlay goes last so it is rendered on top of preceding content due to source order -->\n@if (status !== 'hasData') {\n <div\n class=\"overlay flex-grow {{overlayClassList}}\"\n [attr.role]=\"status === 'error' ? 'alert' : (status === 'pending' ? null : 'status')\"\n [attr.aria-live]=\"status === 'error' ? 'assertive' : (status === 'pending' ? null : 'polite')\"\n [attr.aria-atomic]=\"status === 'pending' ? null : 'true'\"\n\t [ngClass]=\"{'not-mask': !displayAsMask,\n\t\t\t\t'overlay-error': status === 'error',\n\t\t\t\t'overlay-nodata': status === 'noData',\n\t\t\t\t'overlay-pending': status === 'pending'}\">\n <!--Pending Spinner-->\n <ec-spinner [hidden]=\"status !== 'pending'\"></ec-spinner>\n @if (status === 'noData' && noDataTemplate) {\n <ng-container *ngTemplateOutlet=\"noDataTemplate\"></ng-container>\n }\n @if ((status === 'noData' && !noDataTemplate) || status !== 'noData') {\n <!--Status Message-->\n @if (message) {\n <div id=\"statusMessage\"\n class=\"message\"\n [ngClass]=\"{'error': status === 'error', 'mt-1': status === 'pending'}\"\n [innerHtml]=\"message | translate\">\n </div>\n }\n <!-- Action -->\n @if (action?.onClick) {\n <ec-button type=\"common\"\n class=\"mt-3\"\n [icon]=\"action?.icon\"\n (clicked)=\"actionClicked($event)\"\n [label]=\"action?.label\"\n [hidden]=\"status === 'pending'\">\n </ec-button>\n }\n }\n </div>\n}", 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"] }]
4282
4455
  }], propDecorators: { status: [{
4283
4456
  type: Input
4284
4457
  }], message: [{
@@ -5334,7 +5507,7 @@ class DateInputComponent extends FormControlBase {
5334
5507
  * Date parsing formats for user-entered dates. Defaults to month-first formats, but is updated in onInit
5335
5508
  * to use the user's preferred date format.
5336
5509
  */
5337
- this.parseFormats = DateTimeHelper.getMomentParseFormats();
5510
+ this.parseFormats = DateTimeHelper.getLuxonParseFormats();
5338
5511
  }
5339
5512
  ngOnChanges(changes) {
5340
5513
  if (changes.selectionMode && !changes.selectionMode.isFirstChange()) {
@@ -5350,12 +5523,15 @@ class DateInputComponent extends FormControlBase {
5350
5523
  this.onFormModelStatusChanges();
5351
5524
  this.onFormModelValueChanges();
5352
5525
  this.onTextboxValueChanges();
5353
- // Sync the initial disabled status and value of the textbox
5526
+ // Sync the initial disabled status and value of the textbox.
5527
+ // Normalize the form model value so any ISO date strings the host bound to the model
5528
+ // become real Dates before they reach the calendar/stepper logic (see normalizeSelection).
5354
5529
  this.syncTextboxControlDisabledStatus(this.formModel.status);
5355
- const initialDisplayValue = this.selectionStrategy.formatSelection(this.formModel.value);
5530
+ const initialSelection = DateInput.normalizeSelection(this.formModel.value);
5531
+ const initialDisplayValue = this.selectionStrategy.formatSelection(initialSelection);
5356
5532
  this.textboxGroup.patchValue(initialDisplayValue, { emitEvent: false });
5357
5533
  // Update the calendar selection and view based on the initial form model value
5358
- this.calendarSelection = this.formModel.value;
5534
+ this.calendarSelection = initialSelection;
5359
5535
  this.updateCalendars(this.calendarSelection);
5360
5536
  this.updateSteppers(this.calendarSelection);
5361
5537
  }
@@ -5451,7 +5627,7 @@ class DateInputComponent extends FormControlBase {
5451
5627
  goToToday() {
5452
5628
  // If both calendars are visible, show today in the secondary calendar and the month before in the primary calendar.
5453
5629
  if (this.selectionStrategy.showSecondaryCalendar) {
5454
- this.primaryCalendarView = { mode: 'day', date: moment().subtract(1, 'month').toDate() };
5630
+ this.primaryCalendarView = { mode: 'day', date: DateTime.now().minus({ months: 1 }).toJSDate() };
5455
5631
  this.secondaryCalendarView = { mode: 'day', date: new Date() };
5456
5632
  // If we're in a single date mode, show today in the primary calendar.
5457
5633
  }
@@ -5500,11 +5676,14 @@ class DateInputComponent extends FormControlBase {
5500
5676
  // Update the calendar selection textbox value with a formatted date when the form model changes.
5501
5677
  // This is triggered by the user selecting a date from the calendar or when the textbox is blurred.
5502
5678
  this.formModel.valueChanges.pipe(takeUntil(this.componentDestroyed)).subscribe(value => {
5503
- const displayValue = this.selectionStrategy.formatSelection(value);
5679
+ // Normalize first: the host may set an ISO date string on the model (e.g. an API value),
5680
+ // and the calendar logic requires real Dates to avoid an Invalid Date crash.
5681
+ const selection = DateInput.normalizeSelection(value);
5682
+ const displayValue = this.selectionStrategy.formatSelection(selection);
5504
5683
  // Don't emit an event when setting the textbox value to avoid circular updates between the textbox and form model.
5505
5684
  this.textboxGroup.patchValue(displayValue, { emitEvent: false });
5506
- this.updateCalendars(value);
5507
- this.updateSteppers(value);
5685
+ this.updateCalendars(selection);
5686
+ this.updateSteppers(selection);
5508
5687
  });
5509
5688
  }
5510
5689
  onTextboxValueChanges() {
@@ -5584,7 +5763,7 @@ class DateInputComponent extends FormControlBase {
5584
5763
  const { preference } = await lastValueFrom(this.userPreferenceService.getPreferences());
5585
5764
  const dateFormat = preference?.dateFormat;
5586
5765
  this.placeholder = dateFormat ?? this.placeholder;
5587
- this.parseFormats = DateTimeHelper.getMomentParseFormats(dateFormat);
5766
+ this.parseFormats = DateTimeHelper.getLuxonParseFormats(dateFormat);
5588
5767
  }
5589
5768
  /** Focuses the date input. */
5590
5769
  focusInput() {
@@ -5604,10 +5783,10 @@ class DateInputComponent extends FormControlBase {
5604
5783
  // This would cause the dates in the other calendar to be unselectable if we were to
5605
5784
  // update the min/max dates in these cases.
5606
5785
  if (this.primaryCalendarView?.mode === 'day') {
5607
- this.secondaryCalendarMinDate = moment(this.primaryCalendarView?.date).add(1, 'month').startOf('month').toDate();
5786
+ this.secondaryCalendarMinDate = DateTime.fromJSDate(this.primaryCalendarView.date).plus({ months: 1 }).startOf('month').toJSDate();
5608
5787
  }
5609
5788
  if (this.secondaryCalendarView?.mode === 'day') {
5610
- this.primaryCalendarMaxDate = moment(this.secondaryCalendarView?.date).subtract(1, 'month').endOf('month').toDate();
5789
+ this.primaryCalendarMaxDate = DateTime.fromJSDate(this.secondaryCalendarView.date).minus({ months: 1 }).endOf('month').toJSDate();
5611
5790
  }
5612
5791
  }
5613
5792
  updateCalendars(selection) {
@@ -5623,8 +5802,10 @@ class DateInputComponent extends FormControlBase {
5623
5802
  // However, it's possible that the new date is after the secondary calendar's date. In this case,
5624
5803
  // We want to shift the secondary calendar so the calendars stay in order.
5625
5804
  if (shiftSecondary && this.secondaryCalendarView) {
5626
- if (moment(this.primaryCalendarView.date).isSameOrAfter(this.secondaryCalendarView.date, 'month')) {
5627
- let newSecondaryDate = moment(this.primaryCalendarView.date).add(1, 'month').toDate();
5805
+ const primaryDt = DateTime.fromJSDate(this.primaryCalendarView.date);
5806
+ const secondaryDt = DateTime.fromJSDate(this.secondaryCalendarView.date);
5807
+ if (primaryDt.startOf('month') >= secondaryDt.startOf('month')) {
5808
+ const newSecondaryDate = primaryDt.plus({ months: 1 }).toJSDate();
5628
5809
  this.secondaryCalendarView = { mode: this.selectionStrategy.selectionViewMode, date: newSecondaryDate };
5629
5810
  }
5630
5811
  }
@@ -5635,8 +5816,10 @@ class DateInputComponent extends FormControlBase {
5635
5816
  // However, it's possible that the new date is before the primary calendar's date. In this case,
5636
5817
  // We want to shift the primary calendar so the calendars stay in order.
5637
5818
  if (shiftPrimary && this.primaryCalendarView) {
5638
- if (moment(this.primaryCalendarView.date).isSameOrAfter(this.secondaryCalendarView.date, 'month')) {
5639
- let newPrimaryDate = moment(this.secondaryCalendarView.date).subtract(1, 'month').toDate();
5819
+ const primaryDt = DateTime.fromJSDate(this.primaryCalendarView.date);
5820
+ const secondaryDt = DateTime.fromJSDate(this.secondaryCalendarView.date);
5821
+ if (primaryDt.startOf('month') >= secondaryDt.startOf('month')) {
5822
+ const newPrimaryDate = secondaryDt.minus({ months: 1 }).toJSDate();
5640
5823
  this.primaryCalendarView = { mode: this.selectionStrategy.selectionViewMode, date: newPrimaryDate };
5641
5824
  }
5642
5825
  }
@@ -5686,10 +5869,12 @@ class DateInputComponent extends FormControlBase {
5686
5869
  // We're using the view mode as the granularity. We don't want to invalidate
5687
5870
  // the date in the case where you have a month/quarter/year selected and the min
5688
5871
  // or max date is somewhere within that month/quarter/year.
5689
- if (moment(value).isBefore(this.minDate, this.selectionStrategy.selectionViewMode)) {
5872
+ const unit = this.selectionStrategy.selectionViewMode;
5873
+ const valueDt = DateTime.fromJSDate(value).startOf(unit);
5874
+ if (valueDt < DateTime.fromJSDate(this.minDate).startOf(unit)) {
5690
5875
  return { minDate: { minValue: this.minDate } };
5691
5876
  }
5692
- else if (moment(value).isAfter(this.maxDate, this.selectionStrategy.selectionViewMode)) {
5877
+ else if (valueDt > DateTime.fromJSDate(this.maxDate).startOf(unit)) {
5693
5878
  return { maxDate: { maxValue: this.maxDate } };
5694
5879
  }
5695
5880
  }
@@ -10028,11 +10213,11 @@ class SelectComponent extends FormControlBase {
10028
10213
  }
10029
10214
  }
10030
10215
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SelectComponent, deps: [{ token: ValidationMessageService }, { token: FormGroupHelper }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
10031
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: SelectComponent, isStandalone: false, selector: "ec-select", inputs: { placeholder: "placeholder", options: "options", autoDefault: "autoDefault" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"control\"\n [ngClass]=\"{'is-readonly': readonly, 'is-disabled': formModel.disabled}\">\n\n @if (label) {\n <label\n for=\"{{id}}_select\">\n <span>{{label | translate}}</span>\n @if (helpPopover) {\n <ec-help-popover id=\"{{id}}_helpPopover\"\n class=\"d-inline-block my-n3 mx-n1\"\n text=\"{{helpPopover | translate}}\"\n contentPosition=\"{{helpPopoverPosition}}\">\n </ec-help-popover>\n }\n </label>\n }\n\n <select [attr.id]=\"inputId\"\n tabindex=\"{{tabindex}}\"\n [formControl]=\"formModel\"\n [attr.required]=\"required\"\n [attr.cdkFocusInitial]=\"autofocus || null\">\n @if (!required) {\n <option\n [ngValue]=\"null\">{{placeholder}}</option>\n }\n @for (option of options; track option) {\n <option\n [ngValue]=\"option.value\">{{option.label}}</option>\n }\n </select>\n <i class=\"ec-icon icon-caret-down\"></i>\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)}: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:hover:not(:disabled):not(:focus),:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}: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-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~.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:not(:focus){border-color:var(--ec-form-control-border-color-invalid)}: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:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host .textbox-group{display:flex;position:relative}:host textarea:focus,:host input:focus,:host select:focus{outline:none}:host .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}:host .control{position:relative}: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 select{-webkit-appearance:none;-moz-appearance:none;appearance:none}:host select::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host select::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host select::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host select:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host select:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host select{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 select~.icon-required,:host select~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host select:required.is-empty{background-image:none;background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid)}:host select.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host select.ng-invalid.ng-touched:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host select.ng-invalid.ng-touched{background-image:none;background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select.is-pending.ng-valid,:host select.is-pending.ng-invalid,:host select.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select:focus,:host select: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 select: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 select:disabled:required,:host select: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 select:disabled:required+.icon-required,:host select:disabled:required.is-empty+.icon-required{display:none}:host select:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host select.is-uppercase:not(.is-empty){text-transform:uppercase}:host select{padding-right:1.5rem}:host select:focus{position:static}:host .icon-caret-down{position:absolute;right:.5rem;bottom:.5625rem;z-index:1;pointer-events:none}:host select:disabled~.icon-caret-down{opacity:var(--ec-form-control-opacity-disabled)}:host .is-readonly .icon-caret-down{display:none}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { 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: i3.TranslatePipe, name: "translate" }] }); }
10216
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: SelectComponent, isStandalone: false, selector: "ec-select", inputs: { placeholder: "placeholder", options: "options", autoDefault: "autoDefault" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"control\"\n [ngClass]=\"{'is-readonly': readonly, 'is-disabled': formModel.disabled}\">\n\n @if (label) {\n <label\n for=\"{{id}}_select\">\n <span>{{label | translate}}</span>\n @if (helpPopover) {\n <ec-help-popover id=\"{{id}}_helpPopover\"\n class=\"d-inline-block my-n3 mx-n1\"\n text=\"{{helpPopover | translate}}\"\n contentPosition=\"{{helpPopoverPosition}}\">\n </ec-help-popover>\n }\n </label>\n }\n\n <select [attr.id]=\"inputId\"\n tabindex=\"{{tabindex}}\"\n [formControl]=\"formModel\"\n [attr.required]=\"required\"\n [attr.cdkFocusInitial]=\"autofocus || null\">\n @if (!required) {\n <option\n [ngValue]=\"null\">{{placeholder}}</option>\n }\n @for (option of options; track option) {\n <option\n [ngValue]=\"option.value\">{{option.label}}</option>\n }\n </select>\n <i class=\"ec-icon icon-caret-down\" aria-hidden=\"true\"></i>\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)}: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:hover:not(:disabled):not(:focus),:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}: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-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~.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:not(:focus){border-color:var(--ec-form-control-border-color-invalid)}: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:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host .textbox-group{display:flex;position:relative}:host textarea:focus,:host input:focus,:host select:focus{outline:none}:host .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}:host .control{position:relative}: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 select{-webkit-appearance:none;-moz-appearance:none;appearance:none}:host select::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host select::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host select::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host select:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host select:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host select{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 select~.icon-required,:host select~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host select:required.is-empty{background-image:none;background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid)}:host select.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host select.ng-invalid.ng-touched:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host select.ng-invalid.ng-touched{background-image:none;background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select.is-pending.ng-valid,:host select.is-pending.ng-invalid,:host select.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select:focus,:host select: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 select: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 select:disabled:required,:host select: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 select:disabled:required+.icon-required,:host select:disabled:required.is-empty+.icon-required{display:none}:host select:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host select.is-uppercase:not(.is-empty){text-transform:uppercase}:host select{padding-right:1.5rem}:host select:focus{position:static}:host .icon-caret-down{position:absolute;right:.5rem;bottom:.5625rem;z-index:1;pointer-events:none}:host select:disabled~.icon-caret-down{opacity:var(--ec-form-control-opacity-disabled)}:host .is-readonly .icon-caret-down{display:none}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { 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: i3.TranslatePipe, name: "translate" }] }); }
10032
10217
  }
10033
10218
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SelectComponent, decorators: [{
10034
10219
  type: Component,
10035
- args: [{ selector: 'ec-select', standalone: false, template: "<div class=\"control\"\n [ngClass]=\"{'is-readonly': readonly, 'is-disabled': formModel.disabled}\">\n\n @if (label) {\n <label\n for=\"{{id}}_select\">\n <span>{{label | translate}}</span>\n @if (helpPopover) {\n <ec-help-popover id=\"{{id}}_helpPopover\"\n class=\"d-inline-block my-n3 mx-n1\"\n text=\"{{helpPopover | translate}}\"\n contentPosition=\"{{helpPopoverPosition}}\">\n </ec-help-popover>\n }\n </label>\n }\n\n <select [attr.id]=\"inputId\"\n tabindex=\"{{tabindex}}\"\n [formControl]=\"formModel\"\n [attr.required]=\"required\"\n [attr.cdkFocusInitial]=\"autofocus || null\">\n @if (!required) {\n <option\n [ngValue]=\"null\">{{placeholder}}</option>\n }\n @for (option of options; track option) {\n <option\n [ngValue]=\"option.value\">{{option.label}}</option>\n }\n </select>\n <i class=\"ec-icon icon-caret-down\"></i>\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)}: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:hover:not(:disabled):not(:focus),:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}: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-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~.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:not(:focus){border-color:var(--ec-form-control-border-color-invalid)}: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:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host .textbox-group{display:flex;position:relative}:host textarea:focus,:host input:focus,:host select:focus{outline:none}:host .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}:host .control{position:relative}: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 select{-webkit-appearance:none;-moz-appearance:none;appearance:none}:host select::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host select::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host select::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host select:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host select:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host select{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 select~.icon-required,:host select~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host select:required.is-empty{background-image:none;background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid)}:host select.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host select.ng-invalid.ng-touched:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host select.ng-invalid.ng-touched{background-image:none;background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select.is-pending.ng-valid,:host select.is-pending.ng-invalid,:host select.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select:focus,:host select: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 select: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 select:disabled:required,:host select: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 select:disabled:required+.icon-required,:host select:disabled:required.is-empty+.icon-required{display:none}:host select:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host select.is-uppercase:not(.is-empty){text-transform:uppercase}:host select{padding-right:1.5rem}:host select:focus{position:static}:host .icon-caret-down{position:absolute;right:.5rem;bottom:.5625rem;z-index:1;pointer-events:none}:host select:disabled~.icon-caret-down{opacity:var(--ec-form-control-opacity-disabled)}:host .is-readonly .icon-caret-down{display:none}\n"] }]
10220
+ args: [{ selector: 'ec-select', standalone: false, template: "<div class=\"control\"\n [ngClass]=\"{'is-readonly': readonly, 'is-disabled': formModel.disabled}\">\n\n @if (label) {\n <label\n for=\"{{id}}_select\">\n <span>{{label | translate}}</span>\n @if (helpPopover) {\n <ec-help-popover id=\"{{id}}_helpPopover\"\n class=\"d-inline-block my-n3 mx-n1\"\n text=\"{{helpPopover | translate}}\"\n contentPosition=\"{{helpPopoverPosition}}\">\n </ec-help-popover>\n }\n </label>\n }\n\n <select [attr.id]=\"inputId\"\n tabindex=\"{{tabindex}}\"\n [formControl]=\"formModel\"\n [attr.required]=\"required\"\n [attr.cdkFocusInitial]=\"autofocus || null\">\n @if (!required) {\n <option\n [ngValue]=\"null\">{{placeholder}}</option>\n }\n @for (option of options; track option) {\n <option\n [ngValue]=\"option.value\">{{option.label}}</option>\n }\n </select>\n <i class=\"ec-icon icon-caret-down\" aria-hidden=\"true\"></i>\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)}: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:hover:not(:disabled):not(:focus),:host .control.invalid .textbox-group-input ::ng-deep .control input.ng-valid:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}: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-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~.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:not(:focus){border-color:var(--ec-form-control-border-color-invalid)}: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:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host .textbox-group{display:flex;position:relative}:host textarea:focus,:host input:focus,:host select:focus{outline:none}:host .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}:host .control{position:relative}: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 select{-webkit-appearance:none;-moz-appearance:none;appearance:none}:host select::selection{background-color:var(--ec-form-control-background-color-selection);color:var(--ec-form-control-color-selection)}:host select::-webkit-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host select::-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host select:-ms-input-placeholder{color:var(--ec-form-control-color-placeholder)}:host select:-moz-placeholder{color:var(--ec-form-control-color-placeholder);opacity:1}:host select{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 select~.icon-required,:host select~.icon-invalid{color:var(--ec-form-control-border-color-invalid)}:host select:required.is-empty{background-image:none;background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select.ng-invalid.ng-touched{background-color:var(--ec-form-control-background-color-invalid);border-color:var(--ec-form-control-border-color-invalid)}:host select.ng-invalid.ng-touched:focus{border-color:var(--ec-form-control-background-color-invalid)}:host select.ng-invalid.ng-touched:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host select.ng-invalid.ng-touched{background-image:none;background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select.is-pending.ng-valid,:host select.is-pending.ng-invalid,:host select.is-pending.ng-pending{background-image:\"\";background-repeat:no-repeat;background-position:.5rem center;background-size:1rem,1rem;padding-left:1.75rem}:host select:focus,:host select: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 select: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 select:disabled:required,:host select: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 select:disabled:required+.icon-required,:host select:disabled:required.is-empty+.icon-required{display:none}:host select:hover:not(:disabled):not(:focus){border-color:var(--ec-form-control-border-color-hover)}:host select.is-uppercase:not(.is-empty){text-transform:uppercase}:host select{padding-right:1.5rem}:host select:focus{position:static}:host .icon-caret-down{position:absolute;right:.5rem;bottom:.5625rem;z-index:1;pointer-events:none}:host select:disabled~.icon-caret-down{opacity:var(--ec-form-control-opacity-disabled)}:host .is-readonly .icon-caret-down{display:none}\n"] }]
10036
10221
  }], ctorParameters: () => [{ type: ValidationMessageService }, { type: FormGroupHelper }, { type: i0.ElementRef }], propDecorators: { placeholder: [{
10037
10222
  type: Input
10038
10223
  }], options: [{
@@ -10267,6 +10452,8 @@ class ConfirmComponent {
10267
10452
  this.formGroup = new UntypedFormGroup({});
10268
10453
  this.status = new Overlay('hasData');
10269
10454
  this.showTextBox = false;
10455
+ /** Result of the inline confirm action, used for the screen-reader status text in the result template */
10456
+ this.inlineResultSuccess = false;
10270
10457
  this.destroyed = new Subject();
10271
10458
  this.onDialogSave = new EventEmitter();
10272
10459
  this.onDialogCancel = new EventEmitter();
@@ -10345,6 +10532,7 @@ class ConfirmComponent {
10345
10532
  async doInlineConfirmAction() {
10346
10533
  this.status.setStatus('pending', this.context.inlineConfirmPendingMessage);
10347
10534
  let result = await this.context.inlineConfirmAction();
10535
+ this.inlineResultSuccess = result.isSuccess;
10348
10536
  this.status.setStatus('noData', result.message, {
10349
10537
  id: 'inlineConfirmClose',
10350
10538
  label: 'Close',
@@ -10354,11 +10542,11 @@ class ConfirmComponent {
10354
10542
  });
10355
10543
  }
10356
10544
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ConfirmComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10357
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: ConfirmComponent, isStandalone: false, selector: "ec-confirm", inputs: { context: "context" }, outputs: { onDialogSave: "onDialogSave", onDialogCancel: "onDialogCancel" }, ngImport: i0, template: "<section ecOverlay\n [status]=\"status?.status\"\n [message]=\"status?.message\"\n [action]=\"status?.action\"\n [noDataTemplate]=\"inlineConfirmResult\"\n overlayClassList=\"p-0\"\n class=\"bg-body flex-grow d-flex\">\n <form [formGroup]=\"formGroup\" class=\"flex-grow flex-column confirm-content text-body-1\">\n <section class=\"flex-grow\">\n @if (context?.title) {\n <h2 class=\"font-weight-bold mb-3\">{{context?.title}}</h2>\n }\n @if (error) {\n <ec-banner [text]=\"error | translate\" bannerStyle=\"normal\" class=\"mb-3\"></ec-banner>\n }\n @if (context?.message) {\n <div [innerHtml]=\"context?.message | translate\" class=\"mb-3\"></div>\n }\n\n @if (context?.checkConfirm) {\n <ec-checkbox\n [formModel]=\"formGroup.get('checkbox')\"\n name=\"confirmCheckbox\"\n [label]=\"context?.checkboxText\"\n [autofocus]=\"true\"\n ></ec-checkbox>\n }\n @if (showTextBox) {\n <div [innerHtml]=\"context.confirmLabel\" class=\"mb-3\"></div>\n }\n @if ((context?.textboxType && !context?.checkConfirm) || showTextBox) {\n <ec-textbox id=\"confirmTextbox\"\n [autofocus]=\"true\"\n [formModel]=\"formGroup.get('textbox')\"\n [required]=\"context?.textboxRequired\"\n [label]=\"context?.textboxLabel\"\n [type]=\"context?.textboxType\"\n [rows]=\"context?.textareaRows\"\n [placeholder]=\"context?.textboxPlaceholder\"\n [upperCase]=\"context?.textboxUppercase\">\n </ec-textbox>\n }\n </section>\n <footer class=\"mt-auto flex-shrink d-flex py-2 flex-row-reverse\">\n <ec-button id=\"saveConfirmButton\"\n class=\"ml-2\"\n [type]=\"context?.saveButtonType ? context?.saveButtonType : 'primary'\"\n [label]=\"context?.saveLabel ? context?.saveLabel : 'Save'\"\n [autofocus]=\"!context?.textboxType\"\n [isSubmit]=\"context?.saveOnEnter\"\n (clicked)=\"onSave('primary')\">\n </ec-button>\n @if (context?.alternateSaveLabel) {\n <ec-button id=\"alternateSaveConfirmButton\"\n class=\"ml-2\"\n [type]=\"context?.alternateSaveButtonType ? context?.alternateSaveButtonType : 'primary'\"\n [label]=\"context?.alternateSaveLabel\"\n (clicked)=\"onSave('alternate')\">\n </ec-button>\n }\n @if (!context?.hideCancel) {\n <ec-button\n id=\"cancelConfirmButton\"\n class=\"ml-auto\"\n type=\"secondary\"\n [label]=\"context?.cancelLabel ? context?.cancelLabel : 'Cancel'\"\n (clicked)=\"onCancel()\">\n </ec-button>\n }\n </footer>\n </form>\n</section>\n\n<ng-template #inlineConfirmResult>\n <section class=\"flex-grow align-self-stretch confirm-content text-body-1\">\n <div class=\"d-flex\">\n <i class=\"ec-icon {{status?.action?.icon}} {{status?.action?.classlist}} inline-confirm-result-icon\"></i>\n <div [innerHTML]=\"status?.message | translate\"></div>\n </div>\n <div class=\"d-flex pt-2 mt-auto\">\n <ec-button id=\"inlineConfirmClose\"\n label=\"Close\"\n type=\"secondary\"\n class=\"ml-auto\"\n (clicked)=\"status?.action?.onClick!()\"\n [autofocus]=\"true\">\n </ec-button>\n </div>\n </section>\n</ng-template>", styles: [":host{width:100%;display:flex}.confirm-content{padding:1.5rem;display:flex;flex-direction:column}.inline-confirm-result-icon{margin:.0625rem .25rem .0625rem 0}\n"], dependencies: [{ kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "ec-button", inputs: ["id", "disabled", "lockButton", "icon", "iconColor", "label", "ariaLabel", "ariaExpanded", "ariaHaspopup", "ariaActivedescendant", "ariaControls", "badge", "tabindex", "type", "pending", "pendingIcon", "customTemplate", "isSubmit", "autofocus"], outputs: ["clicked"] }, { kind: "component", type: TextboxComponent, selector: "ec-textbox", inputs: ["autocomplete", "type", "placeholder", "maxlength", "minlength", "rows", "selectOnAutofocus", "upperCase"] }, { kind: "component", type: ViewOverlayComponent, selector: "[ecOverlay]", inputs: ["status", "message", "action", "noDataTemplate", "displayAsMask", "overlayClassList"] }, { kind: "component", type: BannerComponent, selector: "ec-banner", inputs: ["hidden", "id", "type", "bannerStyle", "title", "text", "list", "showCloseBtn", "autoHideOnClose", "customIcon", "rememberClosed"], outputs: ["closed"] }, { kind: "component", type: CheckboxComponent, selector: "ec-checkbox", inputs: ["name", "icon", "dependentCheckboxesGroup", "ignoreDisabledDependents"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] }); }
10545
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: ConfirmComponent, isStandalone: false, selector: "ec-confirm", inputs: { context: "context" }, outputs: { onDialogSave: "onDialogSave", onDialogCancel: "onDialogCancel" }, ngImport: i0, template: "<section ecOverlay\n [status]=\"status?.status\"\n [message]=\"status?.message\"\n [action]=\"status?.action\"\n [noDataTemplate]=\"inlineConfirmResult\"\n overlayClassList=\"p-0\"\n class=\"bg-body flex-grow d-flex\">\n <form [formGroup]=\"formGroup\" class=\"flex-grow flex-column confirm-content text-body-1\">\n <section class=\"flex-grow\">\n @if (context?.title) {\n <h2 class=\"font-weight-bold mb-3\">{{context?.title}}</h2>\n }\n @if (error) {\n <ec-banner [text]=\"error | translate\" bannerStyle=\"normal\" class=\"mb-3\"></ec-banner>\n }\n @if (context?.message) {\n <div [innerHtml]=\"context?.message | translate\" class=\"mb-3\"></div>\n }\n\n @if (context?.checkConfirm) {\n <ec-checkbox\n [formModel]=\"formGroup.get('checkbox')\"\n name=\"confirmCheckbox\"\n [label]=\"context?.checkboxText\"\n [autofocus]=\"true\"\n ></ec-checkbox>\n }\n @if (showTextBox) {\n <div [innerHtml]=\"context.confirmLabel\" class=\"mb-3\"></div>\n }\n @if ((context?.textboxType && !context?.checkConfirm) || showTextBox) {\n <ec-textbox id=\"confirmTextbox\"\n [autofocus]=\"true\"\n [formModel]=\"formGroup.get('textbox')\"\n [required]=\"context?.textboxRequired\"\n [label]=\"context?.textboxLabel\"\n [type]=\"context?.textboxType\"\n [rows]=\"context?.textareaRows\"\n [placeholder]=\"context?.textboxPlaceholder\"\n [upperCase]=\"context?.textboxUppercase\">\n </ec-textbox>\n }\n </section>\n <footer class=\"mt-auto flex-shrink d-flex py-2 flex-row-reverse\">\n <ec-button id=\"saveConfirmButton\"\n class=\"ml-2\"\n [type]=\"context?.saveButtonType ? context?.saveButtonType : 'primary'\"\n [label]=\"context?.saveLabel ? context?.saveLabel : 'Save'\"\n [autofocus]=\"!context?.textboxType\"\n [isSubmit]=\"context?.saveOnEnter\"\n (clicked)=\"onSave('primary')\">\n </ec-button>\n @if (context?.alternateSaveLabel) {\n <ec-button id=\"alternateSaveConfirmButton\"\n class=\"ml-2\"\n [type]=\"context?.alternateSaveButtonType ? context?.alternateSaveButtonType : 'primary'\"\n [label]=\"context?.alternateSaveLabel\"\n (clicked)=\"onSave('alternate')\">\n </ec-button>\n }\n @if (!context?.hideCancel) {\n <ec-button\n id=\"cancelConfirmButton\"\n class=\"ml-auto\"\n type=\"secondary\"\n [label]=\"context?.cancelLabel ? context?.cancelLabel : 'Cancel'\"\n (clicked)=\"onCancel()\">\n </ec-button>\n }\n </footer>\n </form>\n</section>\n\n<ng-template #inlineConfirmResult>\n <section class=\"flex-grow align-self-stretch confirm-content text-body-1\">\n <div class=\"d-flex\">\n <i class=\"ec-icon {{status?.action?.icon}} {{status?.action?.classlist}} inline-confirm-result-icon\" aria-hidden=\"true\"></i>\n <span class=\"sr-only\">{{(inlineResultSuccess ? 'Success_TC' : 'Error_TC') | translate}}</span>\n <div [innerHTML]=\"status?.message | translate\"></div>\n </div>\n <div class=\"d-flex pt-2 mt-auto\">\n <ec-button id=\"inlineConfirmClose\"\n label=\"Close\"\n type=\"secondary\"\n class=\"ml-auto\"\n (clicked)=\"status?.action?.onClick!()\"\n [autofocus]=\"true\">\n </ec-button>\n </div>\n </section>\n</ng-template>", styles: [":host{width:100%;display:flex}.confirm-content{padding:1.5rem;display:flex;flex-direction:column}.inline-confirm-result-icon{margin:.0625rem .25rem .0625rem 0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "ec-button", inputs: ["id", "disabled", "lockButton", "icon", "iconColor", "label", "ariaLabel", "ariaExpanded", "ariaHaspopup", "ariaActivedescendant", "ariaControls", "badge", "tabindex", "type", "pending", "pendingIcon", "customTemplate", "isSubmit", "autofocus"], outputs: ["clicked"] }, { kind: "component", type: TextboxComponent, selector: "ec-textbox", inputs: ["autocomplete", "type", "placeholder", "maxlength", "minlength", "rows", "selectOnAutofocus", "upperCase"] }, { kind: "component", type: ViewOverlayComponent, selector: "[ecOverlay]", inputs: ["status", "message", "action", "noDataTemplate", "displayAsMask", "overlayClassList"] }, { kind: "component", type: BannerComponent, selector: "ec-banner", inputs: ["hidden", "id", "type", "bannerStyle", "title", "text", "list", "showCloseBtn", "autoHideOnClose", "customIcon", "rememberClosed"], outputs: ["closed"] }, { kind: "component", type: CheckboxComponent, selector: "ec-checkbox", inputs: ["name", "icon", "dependentCheckboxesGroup", "ignoreDisabledDependents"] }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] }); }
10358
10546
  }
10359
10547
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ConfirmComponent, decorators: [{
10360
10548
  type: Component,
10361
- args: [{ selector: 'ec-confirm', standalone: false, template: "<section ecOverlay\n [status]=\"status?.status\"\n [message]=\"status?.message\"\n [action]=\"status?.action\"\n [noDataTemplate]=\"inlineConfirmResult\"\n overlayClassList=\"p-0\"\n class=\"bg-body flex-grow d-flex\">\n <form [formGroup]=\"formGroup\" class=\"flex-grow flex-column confirm-content text-body-1\">\n <section class=\"flex-grow\">\n @if (context?.title) {\n <h2 class=\"font-weight-bold mb-3\">{{context?.title}}</h2>\n }\n @if (error) {\n <ec-banner [text]=\"error | translate\" bannerStyle=\"normal\" class=\"mb-3\"></ec-banner>\n }\n @if (context?.message) {\n <div [innerHtml]=\"context?.message | translate\" class=\"mb-3\"></div>\n }\n\n @if (context?.checkConfirm) {\n <ec-checkbox\n [formModel]=\"formGroup.get('checkbox')\"\n name=\"confirmCheckbox\"\n [label]=\"context?.checkboxText\"\n [autofocus]=\"true\"\n ></ec-checkbox>\n }\n @if (showTextBox) {\n <div [innerHtml]=\"context.confirmLabel\" class=\"mb-3\"></div>\n }\n @if ((context?.textboxType && !context?.checkConfirm) || showTextBox) {\n <ec-textbox id=\"confirmTextbox\"\n [autofocus]=\"true\"\n [formModel]=\"formGroup.get('textbox')\"\n [required]=\"context?.textboxRequired\"\n [label]=\"context?.textboxLabel\"\n [type]=\"context?.textboxType\"\n [rows]=\"context?.textareaRows\"\n [placeholder]=\"context?.textboxPlaceholder\"\n [upperCase]=\"context?.textboxUppercase\">\n </ec-textbox>\n }\n </section>\n <footer class=\"mt-auto flex-shrink d-flex py-2 flex-row-reverse\">\n <ec-button id=\"saveConfirmButton\"\n class=\"ml-2\"\n [type]=\"context?.saveButtonType ? context?.saveButtonType : 'primary'\"\n [label]=\"context?.saveLabel ? context?.saveLabel : 'Save'\"\n [autofocus]=\"!context?.textboxType\"\n [isSubmit]=\"context?.saveOnEnter\"\n (clicked)=\"onSave('primary')\">\n </ec-button>\n @if (context?.alternateSaveLabel) {\n <ec-button id=\"alternateSaveConfirmButton\"\n class=\"ml-2\"\n [type]=\"context?.alternateSaveButtonType ? context?.alternateSaveButtonType : 'primary'\"\n [label]=\"context?.alternateSaveLabel\"\n (clicked)=\"onSave('alternate')\">\n </ec-button>\n }\n @if (!context?.hideCancel) {\n <ec-button\n id=\"cancelConfirmButton\"\n class=\"ml-auto\"\n type=\"secondary\"\n [label]=\"context?.cancelLabel ? context?.cancelLabel : 'Cancel'\"\n (clicked)=\"onCancel()\">\n </ec-button>\n }\n </footer>\n </form>\n</section>\n\n<ng-template #inlineConfirmResult>\n <section class=\"flex-grow align-self-stretch confirm-content text-body-1\">\n <div class=\"d-flex\">\n <i class=\"ec-icon {{status?.action?.icon}} {{status?.action?.classlist}} inline-confirm-result-icon\"></i>\n <div [innerHTML]=\"status?.message | translate\"></div>\n </div>\n <div class=\"d-flex pt-2 mt-auto\">\n <ec-button id=\"inlineConfirmClose\"\n label=\"Close\"\n type=\"secondary\"\n class=\"ml-auto\"\n (clicked)=\"status?.action?.onClick!()\"\n [autofocus]=\"true\">\n </ec-button>\n </div>\n </section>\n</ng-template>", styles: [":host{width:100%;display:flex}.confirm-content{padding:1.5rem;display:flex;flex-direction:column}.inline-confirm-result-icon{margin:.0625rem .25rem .0625rem 0}\n"] }]
10549
+ args: [{ selector: 'ec-confirm', standalone: false, template: "<section ecOverlay\n [status]=\"status?.status\"\n [message]=\"status?.message\"\n [action]=\"status?.action\"\n [noDataTemplate]=\"inlineConfirmResult\"\n overlayClassList=\"p-0\"\n class=\"bg-body flex-grow d-flex\">\n <form [formGroup]=\"formGroup\" class=\"flex-grow flex-column confirm-content text-body-1\">\n <section class=\"flex-grow\">\n @if (context?.title) {\n <h2 class=\"font-weight-bold mb-3\">{{context?.title}}</h2>\n }\n @if (error) {\n <ec-banner [text]=\"error | translate\" bannerStyle=\"normal\" class=\"mb-3\"></ec-banner>\n }\n @if (context?.message) {\n <div [innerHtml]=\"context?.message | translate\" class=\"mb-3\"></div>\n }\n\n @if (context?.checkConfirm) {\n <ec-checkbox\n [formModel]=\"formGroup.get('checkbox')\"\n name=\"confirmCheckbox\"\n [label]=\"context?.checkboxText\"\n [autofocus]=\"true\"\n ></ec-checkbox>\n }\n @if (showTextBox) {\n <div [innerHtml]=\"context.confirmLabel\" class=\"mb-3\"></div>\n }\n @if ((context?.textboxType && !context?.checkConfirm) || showTextBox) {\n <ec-textbox id=\"confirmTextbox\"\n [autofocus]=\"true\"\n [formModel]=\"formGroup.get('textbox')\"\n [required]=\"context?.textboxRequired\"\n [label]=\"context?.textboxLabel\"\n [type]=\"context?.textboxType\"\n [rows]=\"context?.textareaRows\"\n [placeholder]=\"context?.textboxPlaceholder\"\n [upperCase]=\"context?.textboxUppercase\">\n </ec-textbox>\n }\n </section>\n <footer class=\"mt-auto flex-shrink d-flex py-2 flex-row-reverse\">\n <ec-button id=\"saveConfirmButton\"\n class=\"ml-2\"\n [type]=\"context?.saveButtonType ? context?.saveButtonType : 'primary'\"\n [label]=\"context?.saveLabel ? context?.saveLabel : 'Save'\"\n [autofocus]=\"!context?.textboxType\"\n [isSubmit]=\"context?.saveOnEnter\"\n (clicked)=\"onSave('primary')\">\n </ec-button>\n @if (context?.alternateSaveLabel) {\n <ec-button id=\"alternateSaveConfirmButton\"\n class=\"ml-2\"\n [type]=\"context?.alternateSaveButtonType ? context?.alternateSaveButtonType : 'primary'\"\n [label]=\"context?.alternateSaveLabel\"\n (clicked)=\"onSave('alternate')\">\n </ec-button>\n }\n @if (!context?.hideCancel) {\n <ec-button\n id=\"cancelConfirmButton\"\n class=\"ml-auto\"\n type=\"secondary\"\n [label]=\"context?.cancelLabel ? context?.cancelLabel : 'Cancel'\"\n (clicked)=\"onCancel()\">\n </ec-button>\n }\n </footer>\n </form>\n</section>\n\n<ng-template #inlineConfirmResult>\n <section class=\"flex-grow align-self-stretch confirm-content text-body-1\">\n <div class=\"d-flex\">\n <i class=\"ec-icon {{status?.action?.icon}} {{status?.action?.classlist}} inline-confirm-result-icon\" aria-hidden=\"true\"></i>\n <span class=\"sr-only\">{{(inlineResultSuccess ? 'Success_TC' : 'Error_TC') | translate}}</span>\n <div [innerHTML]=\"status?.message | translate\"></div>\n </div>\n <div class=\"d-flex pt-2 mt-auto\">\n <ec-button id=\"inlineConfirmClose\"\n label=\"Close\"\n type=\"secondary\"\n class=\"ml-auto\"\n (clicked)=\"status?.action?.onClick!()\"\n [autofocus]=\"true\">\n </ec-button>\n </div>\n </section>\n</ng-template>", styles: [":host{width:100%;display:flex}.confirm-content{padding:1.5rem;display:flex;flex-direction:column}.inline-confirm-result-icon{margin:.0625rem .25rem .0625rem 0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
10362
10550
  }], ctorParameters: () => [], propDecorators: { context: [{
10363
10551
  type: Input
10364
10552
  }], onDialogSave: [{
@@ -11553,6 +11741,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
11553
11741
  args: ['timedToast', { static: false, read: ElementRef }]
11554
11742
  }] } });
11555
11743
 
11744
+ /** Module-level counter so each tooltip instance gets a unique DOM id, keeping
11745
+ * aria-describedby unambiguous when more than one tooltip is in the DOM (e.g. during
11746
+ * the hide delay or across multiple anchors). */
11747
+ let uniqueTooltipId = 0;
11556
11748
  class TooltipDirective {
11557
11749
  constructor(tooltipService, element, renderer2) {
11558
11750
  this.tooltipService = tooltipService;
@@ -11581,6 +11773,10 @@ class TooltipDirective {
11581
11773
  * if we never showed the tooltip this will remain null and we won't have an event to unsubscribe to
11582
11774
  */
11583
11775
  this.mouseMoveUnsubscribe = null;
11776
+ /**Set when we show a tooltip so Escape can dismiss it without moving the pointer (WCAG 1.4.13).
11777
+ * Unsubscribed when hide fires, like mouseMoveUnsubscribe.
11778
+ */
11779
+ this.keydownUnsubscribe = null;
11584
11780
  }
11585
11781
  ngOnInit() {
11586
11782
  //checkInputsValue(): if there is an error it will prevent to execute
@@ -11607,6 +11803,10 @@ class TooltipDirective {
11607
11803
  onMouseMove(event) {
11608
11804
  let callCallback = false;
11609
11805
  if (this.contentRect) {
11806
+ //Keep the tooltip visible while the pointer is over the tooltip content itself (WCAG 1.4.13)
11807
+ if (this.isPointerOverTooltip(event)) {
11808
+ return;
11809
+ }
11610
11810
  callCallback = this.tooltipService.onMove(event, this.contentRect);
11611
11811
  if (callCallback) {
11612
11812
  //trigger on hide process
@@ -11622,9 +11822,39 @@ class TooltipDirective {
11622
11822
  }
11623
11823
  //Trigger the process to hide and later the process
11624
11824
  //to mark as complete the cycle of show and hide the tooltip
11625
- onLeave() {
11825
+ onLeave(event) {
11826
+ //Moving from the anchor onto the tooltip content should not hide it (WCAG 1.4.13).
11827
+ //The document mousemove backstop hides it once the pointer leaves both.
11828
+ const overlayElement = this.tooltip?.overlayRef?.overlayElement;
11829
+ if (overlayElement && event?.relatedTarget && overlayElement.contains(event.relatedTarget)) {
11830
+ return;
11831
+ }
11626
11832
  this.interrupt.next();
11627
11833
  }
11834
+ //Show the tooltip for keyboard users when the anchor receives focus (WCAG 2.1.1)
11835
+ onFocusIn() {
11836
+ this.mouseOver.next();
11837
+ }
11838
+ //Hide the tooltip when keyboard focus leaves the anchor
11839
+ onFocusOut() {
11840
+ this.interrupt.next();
11841
+ }
11842
+ //Dismiss the tooltip on Escape without moving pointer or focus (WCAG 1.4.13)
11843
+ onKeydown(event) {
11844
+ if (event.key === 'Escape') {
11845
+ this.interrupt.next();
11846
+ }
11847
+ }
11848
+ //True when the pointer coordinates are within the tooltip overlay element
11849
+ isPointerOverTooltip(event) {
11850
+ const overlayElement = this.tooltip?.overlayRef?.overlayElement;
11851
+ if (!overlayElement) {
11852
+ return false;
11853
+ }
11854
+ const rect = overlayElement.getBoundingClientRect();
11855
+ return event.clientX >= rect.left && event.clientX <= rect.right
11856
+ && event.clientY >= rect.top && event.clientY <= rect.bottom;
11857
+ }
11628
11858
  checkInputsValue() {
11629
11859
  let parametersWithValue = 0;
11630
11860
  if (this.tooltipText) {
@@ -11651,7 +11881,7 @@ class TooltipDirective {
11651
11881
  */
11652
11882
  createTooltipOptions() {
11653
11883
  this.tooltipOptions = {
11654
- id: `tooltip_${this.tooltipConfig?.id ? this.tooltipConfig?.id : ''}`,
11884
+ id: `tooltip_${this.tooltipConfig?.id ? this.tooltipConfig.id : ++uniqueTooltipId}`,
11655
11885
  text: this.tooltipConfig?.text || this.tooltipText,
11656
11886
  title: this.tooltipConfig?.title,
11657
11887
  subtitle: this.tooltipConfig?.subtitle,
@@ -11672,6 +11902,11 @@ class TooltipDirective {
11672
11902
  if (!this.tooltip) {
11673
11903
  this.tooltip = this.tooltipService.show(element, this.tooltipPosition, this.tooltipOptions);
11674
11904
  this.mouseMoveUnsubscribe = this.renderer2.listen(document, 'mousemove', this.onMouseMove.bind(this));
11905
+ this.keydownUnsubscribe = this.renderer2.listen(document, 'keydown', this.onKeydown.bind(this));
11906
+ //Associate the tooltip content with its anchor so screen readers announce it (WCAG 4.1.2)
11907
+ if (this.tooltipOptions?.id) {
11908
+ this.renderer2.setAttribute(element, 'aria-describedby', this.tooltipOptions.id);
11909
+ }
11675
11910
  }
11676
11911
  }
11677
11912
  hide() {
@@ -11685,6 +11920,11 @@ class TooltipDirective {
11685
11920
  if (this.mouseMoveUnsubscribe) {
11686
11921
  this.mouseMoveUnsubscribe();
11687
11922
  }
11923
+ if (this.keydownUnsubscribe) {
11924
+ this.keydownUnsubscribe();
11925
+ this.keydownUnsubscribe = null;
11926
+ }
11927
+ this.renderer2.removeAttribute(this.element.nativeElement, 'aria-describedby');
11688
11928
  }
11689
11929
  //Interrupt the flow of mouseover with a delay and triggers the
11690
11930
  //flow to hide the tooltip with a delay
@@ -11698,7 +11938,7 @@ class TooltipDirective {
11698
11938
  this.interrupt.next();
11699
11939
  }
11700
11940
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TooltipDirective, deps: [{ token: TooltipService }, { token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
11701
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.4", type: TooltipDirective, isStandalone: false, selector: "[ecTooltip]", inputs: { tooltipConfig: "tooltipConfig", tooltipText: "tooltipText", tooltipCustomContent: "tooltipCustomContent", tooltipPosition: "tooltipPosition" }, host: { listeners: { "mouseover": "onMouseOver()", "mouseleave": "onLeave()" } }, ngImport: i0 }); }
11941
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.4", type: TooltipDirective, isStandalone: false, selector: "[ecTooltip]", inputs: { tooltipConfig: "tooltipConfig", tooltipText: "tooltipText", tooltipCustomContent: "tooltipCustomContent", tooltipPosition: "tooltipPosition" }, host: { listeners: { "mouseover": "onMouseOver()", "mouseleave": "onLeave($event)", "focusin": "onFocusIn()", "focusout": "onFocusOut()" } }, ngImport: i0 }); }
11702
11942
  }
11703
11943
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TooltipDirective, decorators: [{
11704
11944
  type: Directive,
@@ -11719,7 +11959,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
11719
11959
  args: ['mouseover']
11720
11960
  }], onLeave: [{
11721
11961
  type: HostListener,
11722
- args: ['mouseleave']
11962
+ args: ['mouseleave', ['$event']]
11963
+ }], onFocusIn: [{
11964
+ type: HostListener,
11965
+ args: ['focusin']
11966
+ }], onFocusOut: [{
11967
+ type: HostListener,
11968
+ args: ['focusout']
11723
11969
  }] } });
11724
11970
 
11725
11971
  class TreeComponent {
@@ -11998,7 +12244,9 @@ class DateDisplayPipe2 {
11998
12244
  }
11999
12245
  async transform(date, options) {
12000
12246
  const { showEndOfTime, showTime, showSeconds, displayInUTC } = options || {};
12001
- if (!date || !moment(date).isValid() || (!showEndOfTime && DateTimeHelper.isEndOfTime(date))) {
12247
+ if (date == null)
12248
+ return '';
12249
+ if (!DateTimeHelper.toDateTime(date).isValid || (!showEndOfTime && DateTimeHelper.isEndOfTime(date))) {
12002
12250
  return '';
12003
12251
  }
12004
12252
  const { preference } = await lastValueFrom(this.userPrefs.getPreferences());
@@ -12010,8 +12258,7 @@ class DateDisplayPipe2 {
12010
12258
  }
12011
12259
  formatString += ` ${timeFormat}`;
12012
12260
  }
12013
- // Parse and format the date using moment. If displayInUTC is true, use moment.utc() to shift the date to UTC.
12014
- return (displayInUTC ? moment.utc(date) : moment(date)).format(formatString);
12261
+ return DateTimeHelper.formatDate(date, formatString, displayInUTC);
12015
12262
  }
12016
12263
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe2, deps: [{ token: UserPreferenceService }], target: i0.ɵɵFactoryTarget.Pipe }); }
12017
12264
  static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe2, isStandalone: false, name: "dateDisplay2" }); }
@@ -12024,6 +12271,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12024
12271
  }]
12025
12272
  }], ctorParameters: () => [{ type: UserPreferenceService }] });
12026
12273
 
12274
+ /**
12275
+ * Same behavior as {@link DateDisplayPipe2}, but always appends the short named
12276
+ * timezone abbreviation (e.g. `EST`, `PDT`, `UTC`) to the output. Use this when
12277
+ * downstream consumers need to disambiguate the timezone of a rendered date.
12278
+ *
12279
+ * @example <span>{{ someDate | dateDisplay3 | async }}</span>
12280
+ */
12281
+ class DateDisplayPipe3 {
12282
+ constructor(userPrefs) {
12283
+ this.userPrefs = userPrefs;
12284
+ }
12285
+ async transform(date, options) {
12286
+ const { showEndOfTime, showTime, showSeconds, displayInUTC } = options || {};
12287
+ if (date == null)
12288
+ return '';
12289
+ if (!DateTimeHelper.toDateTime(date).isValid || (!showEndOfTime && DateTimeHelper.isEndOfTime(date))) {
12290
+ return '';
12291
+ }
12292
+ const { preference } = await lastValueFrom(this.userPrefs.getPreferences());
12293
+ let formatString = preference?.dateFormat || 'MM/DD/YYYY';
12294
+ if (showTime) {
12295
+ let timeFormat = preference.timeFormat;
12296
+ if (!showSeconds) {
12297
+ timeFormat = timeFormat.replace(':ss', '');
12298
+ }
12299
+ formatString += ` ${timeFormat}`;
12300
+ }
12301
+ // Luxon's `ZZZZ` renders the short named offset (e.g. `EST`, `EDT`, `UTC`).
12302
+ // The token passes through `momentToLuxonFormat` unchanged.
12303
+ formatString += ' ZZZZ';
12304
+ return DateTimeHelper.formatDate(date, formatString, !!displayInUTC);
12305
+ }
12306
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe3, deps: [{ token: UserPreferenceService }], target: i0.ɵɵFactoryTarget.Pipe }); }
12307
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe3, isStandalone: false, name: "dateDisplay3" }); }
12308
+ }
12309
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe3, decorators: [{
12310
+ type: Pipe,
12311
+ args: [{
12312
+ name: 'dateDisplay3',
12313
+ standalone: false
12314
+ }]
12315
+ }], ctorParameters: () => [{ type: UserPreferenceService }] });
12316
+
12027
12317
  /**
12028
12318
  * Format a time to the user's preference for display
12029
12319
  */
@@ -12036,11 +12326,10 @@ class TimeDisplayPipe {
12036
12326
  * Format a time for display, accounting for user's display preferences.
12037
12327
  */
12038
12328
  transform(time, showSeconds) {
12039
- let display = '';
12040
12329
  let formatString = '';
12041
12330
  // use user-preferred formats
12042
12331
  this.userPreferenceService.getPreferences().subscribe(result => {
12043
- //if preferences exist then format time to users preference
12332
+ //if preferences exist then format time to users preference
12044
12333
  //otherwise use the last user preference
12045
12334
  if (result.preference) {
12046
12335
  formatString = result.preference.timeFormat;
@@ -12050,8 +12339,7 @@ class TimeDisplayPipe {
12050
12339
  this.lastFormatString = formatString;
12051
12340
  }
12052
12341
  });
12053
- display = moment(time).format(this.lastFormatString);
12054
- return display;
12342
+ return DateTimeHelper.formatDate(time ?? null, this.lastFormatString);
12055
12343
  }
12056
12344
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TimeDisplayPipe, deps: [{ token: UserPreferenceService }], target: i0.ɵɵFactoryTarget.Pipe }); }
12057
12345
  static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: TimeDisplayPipe, isStandalone: false, name: "timeDisplay" }); }
@@ -12079,21 +12367,21 @@ class RelativeDatePipe {
12079
12367
  * If any of todayLabel, yesterdayLabel is supplied, use the supplied label.
12080
12368
  */
12081
12369
  transform(timeSelected, options) {
12082
- const selected = moment(timeSelected);
12083
- const today = moment();
12084
- const yesterday = moment().subtract(1, 'day');
12370
+ const selected = DateTime.fromISO(timeSelected);
12371
+ const today = DateTime.now();
12372
+ const yesterday = today.minus({ days: 1 });
12085
12373
  const timeDisplay = options?.dateOnly ? `` : ` ${this.timeDisplayPipe.transform(timeSelected)}`;
12086
12374
  let displayValue = null;
12087
12375
  let prefixValue = '';
12088
- if (selected.isSame(today, 'day') && !options?.showTimeForToday) {
12376
+ if (selected.hasSame(today, 'day') && !options?.showTimeForToday) {
12089
12377
  displayValue = this.translateService.instant(options?.todayLabel ? options?.todayLabel : 'today');
12090
12378
  prefixValue = options?.todayPrefix ?? '';
12091
12379
  }
12092
- else if (selected.isSame(today, 'day') && options?.showTimeForToday && !timeDisplay) {
12380
+ else if (selected.hasSame(today, 'day') && options?.showTimeForToday && !timeDisplay) {
12093
12381
  displayValue = this.timeDisplayPipe.transform(timeSelected);
12094
12382
  prefixValue = options?.todayTimePrefix ?? '';
12095
12383
  }
12096
- else if (selected.isSame(yesterday, 'day')) {
12384
+ else if (selected.hasSame(yesterday, 'day')) {
12097
12385
  displayValue = this.translateService.instant(options?.yesterdayLabel ? options?.yesterdayLabel : 'yesterday');
12098
12386
  prefixValue = options?.yesterdayPrefix ?? '';
12099
12387
  }
@@ -12123,13 +12411,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12123
12411
 
12124
12412
  class PageTitleComponent {
12125
12413
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PageTitleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12126
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: PageTitleComponent, isStandalone: false, selector: "app-page-title", inputs: { title: "title", titleIcon: "titleIcon", subTitle: "subTitle", subTitleUrl: "subTitleUrl" }, host: { classAttribute: "d-flex" }, ngImport: i0, template: "@if (titleIcon) {\n <i\n class=\"flex-shrink mr-1 my-1 ec-icon ec-icon-md {{titleIcon}}\"></i>\n}\n<div class=\"text-truncate\">\n @if (title) {\n <h1\n class=\"text-title-1 mb-0 py-1 text-truncate\"\n title=\"{{title}}\">{{title}}</h1>\n }\n @if (subTitle && !subTitleUrl) {\n <p\n class=\"text-caption-1 mb-0 mt-n1 text-truncate\">{{subTitle}}</p>\n }\n @if (subTitle && subTitleUrl) {\n <a\n id=\"subTitle_link\"\n class=\"d-block text-truncate mb-0 mt-n1 font-size-small\"\n routerLink=\"{{subTitleUrl}}\"\n target=\"_self\">\n {{subTitle}}\n </a>\n }\n</div>", dependencies: [{ kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
12414
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: PageTitleComponent, isStandalone: false, selector: "app-page-title", inputs: { title: "title", titleIcon: "titleIcon", subTitle: "subTitle", subTitleUrl: "subTitleUrl" }, host: { classAttribute: "d-flex" }, ngImport: i0, template: "@if (titleIcon) {\n <i\n class=\"flex-shrink mr-1 my-1 ec-icon ec-icon-md {{titleIcon}}\"\n aria-hidden=\"true\"></i>\n}\n<div class=\"text-truncate\">\n @if (title) {\n <h1\n class=\"text-title-1 mb-0 py-1 text-truncate\"\n title=\"{{title}}\">{{title}}</h1>\n }\n @if (subTitle && !subTitleUrl) {\n <p\n class=\"text-caption-1 mb-0 mt-n1 text-truncate\">{{subTitle}}</p>\n }\n @if (subTitle && subTitleUrl) {\n <a\n id=\"subTitle_link\"\n class=\"d-block text-truncate mb-0 mt-n1 font-size-small\"\n routerLink=\"{{subTitleUrl}}\"\n target=\"_self\">\n {{subTitle}}\n </a>\n }\n</div>", dependencies: [{ kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
12127
12415
  }
12128
12416
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: PageTitleComponent, decorators: [{
12129
12417
  type: Component,
12130
12418
  args: [{ selector: 'app-page-title', host: {
12131
12419
  class: 'd-flex'
12132
- }, standalone: false, template: "@if (titleIcon) {\n <i\n class=\"flex-shrink mr-1 my-1 ec-icon ec-icon-md {{titleIcon}}\"></i>\n}\n<div class=\"text-truncate\">\n @if (title) {\n <h1\n class=\"text-title-1 mb-0 py-1 text-truncate\"\n title=\"{{title}}\">{{title}}</h1>\n }\n @if (subTitle && !subTitleUrl) {\n <p\n class=\"text-caption-1 mb-0 mt-n1 text-truncate\">{{subTitle}}</p>\n }\n @if (subTitle && subTitleUrl) {\n <a\n id=\"subTitle_link\"\n class=\"d-block text-truncate mb-0 mt-n1 font-size-small\"\n routerLink=\"{{subTitleUrl}}\"\n target=\"_self\">\n {{subTitle}}\n </a>\n }\n</div>" }]
12420
+ }, standalone: false, template: "@if (titleIcon) {\n <i\n class=\"flex-shrink mr-1 my-1 ec-icon ec-icon-md {{titleIcon}}\"\n aria-hidden=\"true\"></i>\n}\n<div class=\"text-truncate\">\n @if (title) {\n <h1\n class=\"text-title-1 mb-0 py-1 text-truncate\"\n title=\"{{title}}\">{{title}}</h1>\n }\n @if (subTitle && !subTitleUrl) {\n <p\n class=\"text-caption-1 mb-0 mt-n1 text-truncate\">{{subTitle}}</p>\n }\n @if (subTitle && subTitleUrl) {\n <a\n id=\"subTitle_link\"\n class=\"d-block text-truncate mb-0 mt-n1 font-size-small\"\n routerLink=\"{{subTitleUrl}}\"\n target=\"_self\">\n {{subTitle}}\n </a>\n }\n</div>" }]
12133
12421
  }], propDecorators: { title: [{
12134
12422
  type: Input
12135
12423
  }], titleIcon: [{
@@ -12731,6 +13019,7 @@ class ComponentsModule {
12731
13019
  DateInputComponent,
12732
13020
  KeyboardNavContainerDirective,
12733
13021
  DateDisplayPipe2,
13022
+ DateDisplayPipe3,
12734
13023
  MultiselectComponent], imports: [CommonModule,
12735
13024
  FormsModule,
12736
13025
  ReactiveFormsModule,
@@ -12803,7 +13092,8 @@ class ComponentsModule {
12803
13092
  CalendarComponent,
12804
13093
  DateInputComponent,
12805
13094
  KeyboardNavContainerDirective,
12806
- DateDisplayPipe2] }); }
13095
+ DateDisplayPipe2,
13096
+ DateDisplayPipe3] }); }
12807
13097
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ComponentsModule, providers: [
12808
13098
  FormGroupHelper,
12809
13099
  DialogService,
@@ -12813,7 +13103,8 @@ class ComponentsModule {
12813
13103
  RowCountPipe,
12814
13104
  RelativeDatePipe,
12815
13105
  HighlightTextPipe,
12816
- DateDisplayPipe2
13106
+ DateDisplayPipe2,
13107
+ DateDisplayPipe3
12817
13108
  ], imports: [CommonModule,
12818
13109
  FormsModule,
12819
13110
  ReactiveFormsModule,
@@ -12899,6 +13190,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12899
13190
  DateInputComponent,
12900
13191
  KeyboardNavContainerDirective,
12901
13192
  DateDisplayPipe2,
13193
+ DateDisplayPipe3,
12902
13194
  MultiselectComponent
12903
13195
  ],
12904
13196
  imports: [
@@ -12920,7 +13212,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12920
13212
  RowCountPipe,
12921
13213
  RelativeDatePipe,
12922
13214
  HighlightTextPipe,
12923
- DateDisplayPipe2
13215
+ DateDisplayPipe2,
13216
+ DateDisplayPipe3
12924
13217
  ],
12925
13218
  exports: [
12926
13219
  ButtonComponent,
@@ -12988,7 +13281,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12988
13281
  CalendarComponent,
12989
13282
  DateInputComponent,
12990
13283
  KeyboardNavContainerDirective,
12991
- DateDisplayPipe2
13284
+ DateDisplayPipe2,
13285
+ DateDisplayPipe3
12992
13286
  ]
12993
13287
  }]
12994
13288
  }] });
@@ -13984,5 +14278,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
13984
14278
  * Generated bundle index. Do not edit.
13985
14279
  */
13986
14280
 
13987
- export { AppBarComponent, AvatarComponent, BannerComponent, ButtonComponent, CacheService, CalendarComponent, CheckboxComponent, ClickAreaForDirective, CollapsibleToggleComponent, ComboboxComponent, ComponentsModule, ConfirmComponent, ConfirmDialogContext, CopyButtonBaseTestInjectorFactory, CopyButtonDirective, CopyTableButtonDirective, CustomValidators, DateDisplayPipe, DateDisplayPipe2, DateInput, DateInputComponent, DateTimeHelper, DialogCloseDuration, DialogCloseEvent, DialogCloseLatestEvent, DialogComponent, DialogEvent, DialogGroupComponent, DialogOpenDuration, DialogOpenEndEvent, DialogOpenStartEvent, DialogResult, DialogService, DropdownComponent, ErrorService, FileTypeExtensions, FileUploadComponent, FormControlBase, FormControlComponent, FormControlLabelComponent, FormGroupComponent, FormGroupHelper, HelpPopoverComponent, HierarchyBase, HierarchyBaseTestInjectorFactory, HierarchyItem, HierarchyMocks, HierarchyTreeComponent, HighlightTextPipe, IfViewportWidthDirective, ItemDisplayComponent, ItemPickerComponent, ItemPickerSelectableContext, JsonDisplayComponent, JsonHelper, KeyboardNavContainerDirective, LinkButtonComponent, MenuComponent, MockActivatedRoute, MockDateDisplayPipe, MockDialog, MockDialogContent, MockTranslateService, MockTranslationHelperService, MultiselectComponent, NavGroup, NavItemActiveDirective, NumericboxComponent, Overlay, PageBaseComponent, PageBaseComponentTestHelper, PageBaseComponentTestInjectorFactory, PageInitResult, PageStatus, PageStatuses, PageTitleComponent, PageViewComponent, PanelCloseDuration, PanelOpenDuration, PopoverComponent, PopupContainerDirective, RadioButtonComponent, RadioButtonOption, RelativeDatePipe, ResizableBase, ResizableColumnComponent, ResizableComponent, RouterHelper, RowCountPipe, ScrollService, SearchableTableComponent, SelectComponent, SpinnerComponent, SplashComponent, SplashService, SpyFactory, TableComponent, TableLockedColumnComponent, TableMasterHeaderRowComponent, TableMasterRowComponent, TablePaginationComponent, TableSelectableRowComponent, TableSelectableRowContext, TabsComponent, Tag, TagsComponent, TelemetryService, TelemetryTrackerService, TextboxComponent, TimeDisplayPipe, ToastComponent, ToastEvent, ToastService, ToasterComponent, TooltipComponent, TooltipDirective, TooltipService, TreeComponent, UnicodeStrings, UserPreferenceService, ValidationMessageService, ViewOverlayComponent, WindowService, WizardBaseComponent, WizardButtonsComponent, WizardProgressComponent, canadianPostalCodeRegex, clickEvent, dateInputFormatRegex, domainPattern, findAllSpacesPattern, forEachFormControl, getApiError, getControlValue, getDecimalPattern, handleKeyNavigate, integerPattern, isApiError, menuAnimationSpeed, mockRouterFactory, mockRouterHelperFactory, numericboxValidation, orderByIgnoreCase, otherZipCodeRegex, phoneNumberValidationPattern, sortByIgnoreCase, textboxValidation, unitedStatesZipCodeRegex, urlValidationPattern, validateFormGroupValuesAreUnique };
14281
+ export { AppBarComponent, AvatarComponent, BannerComponent, ButtonComponent, CacheService, CalendarComponent, CheckboxComponent, ClickAreaForDirective, CollapsibleToggleComponent, ComboboxComponent, ComponentsModule, ConfirmComponent, ConfirmDialogContext, CopyButtonBaseTestInjectorFactory, CopyButtonDirective, CopyTableButtonDirective, CustomValidators, DateDisplayPipe, DateDisplayPipe2, DateDisplayPipe3, DateInput, DateInputComponent, DateTimeHelper, DialogCloseDuration, DialogCloseEvent, DialogCloseLatestEvent, DialogComponent, DialogEvent, DialogGroupComponent, DialogOpenDuration, DialogOpenEndEvent, DialogOpenStartEvent, DialogResult, DialogService, DropdownComponent, ErrorService, FileTypeExtensions, FileUploadComponent, FormControlBase, FormControlComponent, FormControlLabelComponent, FormGroupComponent, FormGroupHelper, HelpPopoverComponent, HierarchyBase, HierarchyBaseTestInjectorFactory, HierarchyItem, HierarchyMocks, HierarchyTreeComponent, HighlightTextPipe, IfViewportWidthDirective, ItemDisplayComponent, ItemPickerComponent, ItemPickerSelectableContext, JsonDisplayComponent, JsonHelper, KeyboardNavContainerDirective, LinkButtonComponent, MenuComponent, MockActivatedRoute, MockDateDisplayPipe, MockDialog, MockDialogContent, MockTranslateService, MockTranslationHelperService, MultiselectComponent, NavGroup, NavItemActiveDirective, NumericboxComponent, Overlay, PageBaseComponent, PageBaseComponentTestHelper, PageBaseComponentTestInjectorFactory, PageInitResult, PageStatus, PageStatuses, PageTitleComponent, PageViewComponent, PanelCloseDuration, PanelOpenDuration, PopoverComponent, PopupContainerDirective, RadioButtonComponent, RadioButtonOption, RelativeDatePipe, ResizableBase, ResizableColumnComponent, ResizableComponent, RouterHelper, RowCountPipe, ScrollService, SearchableTableComponent, SelectComponent, SpinnerComponent, SplashComponent, SplashService, SpyFactory, TableComponent, TableLockedColumnComponent, TableMasterHeaderRowComponent, TableMasterRowComponent, TablePaginationComponent, TableSelectableRowComponent, TableSelectableRowContext, TabsComponent, Tag, TagsComponent, TelemetryService, TelemetryTrackerService, TextboxComponent, TimeDisplayPipe, ToastComponent, ToastEvent, ToastService, ToasterComponent, TooltipComponent, TooltipDirective, TooltipService, TreeComponent, UnicodeStrings, UserPreferenceService, ValidationMessageService, ViewOverlayComponent, WindowService, WizardBaseComponent, WizardButtonsComponent, WizardProgressComponent, canadianPostalCodeRegex, clickEvent, dateInputFormatRegex, domainPattern, findAllSpacesPattern, forEachFormControl, getApiError, getControlValue, getDecimalPattern, handleKeyNavigate, integerPattern, isApiError, menuAnimationSpeed, mockRouterFactory, mockRouterHelperFactory, numericboxValidation, orderByIgnoreCase, otherZipCodeRegex, phoneNumberValidationPattern, sortByIgnoreCase, textboxValidation, unitedStatesZipCodeRegex, urlValidationPattern, validateFormGroupValuesAreUnique };
13988
14282
  //# sourceMappingURL=energycap-components.mjs.map