@porscheinformatik/clr-addons 19.15.2 → 19.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/charts/area-chart/area-chart.component.d.ts +40 -0
  2. package/charts/bar-chart/bar-chart.component.d.ts +92 -0
  3. package/charts/chart-alert-overlay/chart-alert-overlay.component.d.ts +7 -0
  4. package/charts/chart-export/chart-export-button.component.d.ts +9 -0
  5. package/charts/chart-export/chart-export.service.d.ts +10 -0
  6. package/charts/chart-legend/chart-legend.component.d.ts +12 -0
  7. package/charts/chart-tooltip/chart-tooltip.component.d.ts +16 -0
  8. package/charts/charts.module.d.ts +21 -0
  9. package/charts/combo-chart/combo-chart.component.d.ts +77 -0
  10. package/charts/constants/alert-message-constants.d.ts +5 -0
  11. package/charts/constants/index.d.ts +1 -0
  12. package/charts/directives/auto-position.directive.d.ts +16 -0
  13. package/charts/directives/index.d.ts +4 -0
  14. package/charts/directives/outside-click.directive.d.ts +10 -0
  15. package/charts/directives/screen-state.service.d.ts +35 -0
  16. package/charts/directives/tenant-full-date-range.directive.d.ts +10 -0
  17. package/charts/directives/window-resize.directive.d.ts +12 -0
  18. package/charts/funnel-chart/funnel-chart.component.d.ts +153 -0
  19. package/charts/index.d.ts +8 -0
  20. package/charts/line-chart/line-chart.component.d.ts +40 -0
  21. package/charts/pie-chart/pie-chart.component.d.ts +39 -0
  22. package/charts/shared/chart-base.d.ts +37 -0
  23. package/charts/shared/chart-skeleton.component.d.ts +8 -0
  24. package/charts/shared/d3-chart-axes.d.ts +28 -0
  25. package/charts/shared/d3-dots.d.ts +10 -0
  26. package/charts/shared/xy-chart.types.d.ts +35 -0
  27. package/charts/utils/color.utils.d.ts +9 -0
  28. package/charts/utils/index.d.ts +3 -0
  29. package/charts/utils/text.utils.d.ts +8 -0
  30. package/charts/utils/utils.d.ts +2 -0
  31. package/clr-addons.module.d.ts +4 -3
  32. package/control-enter-submit/clr-control-enter-submit.directive.d.ts +1 -1
  33. package/copy-to-clipboard/copy-to-clipboard.d.ts +30 -0
  34. package/copy-to-clipboard/index.d.ts +1 -0
  35. package/fesm2022/clr-addons.mjs +2834 -308
  36. package/fesm2022/clr-addons.mjs.map +1 -1
  37. package/index.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/styles/clr-addons-phs.css +52 -0
  40. package/styles/clr-addons-phs.css.map +1 -1
  41. package/styles/clr-addons-phs.min.css +1 -1
  42. package/styles/clr-addons-phs.min.css.map +1 -1
  43. package/summary-area/index.d.ts +0 -1
  44. package/summary-area/summary-item-value-copy-button/summary-item-value-copy-button.d.ts +0 -24
@@ -1,14 +1,14 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, NgModule, Injectable, EventEmitter, Output, Input, Directive, ViewChild, ContentChildren, TemplateRef, ViewChildren, HostBinding, ElementRef, Renderer2, forwardRef, Inject, ContentChild, Optional, input, ChangeDetectionStrategy, signal, computed, SkipSelf, inject, model, contentChild, linkedSignal, DestroyRef, contentChildren, effect, output, viewChild, InjectionToken, isSignal, HostListener, LOCALE_ID, Self, Host, NgZone, ChangeDetectorRef, Injector, untracked, runInInjectionContext } from '@angular/core';
2
+ import { Component, NgModule, Injectable, EventEmitter, Output, Input, Directive, ViewChild, ContentChildren, TemplateRef, ViewChildren, HostBinding, ElementRef, Renderer2, forwardRef, Inject, ContentChild, Optional, input, ChangeDetectionStrategy, signal, computed, SkipSelf, inject, model, contentChild, linkedSignal, DestroyRef, contentChildren, effect, output, viewChild, InjectionToken, isSignal, HostListener, LOCALE_ID, Self, Host, ChangeDetectorRef, NgZone, Injector, untracked, runInInjectionContext } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
- import { CommonModule, DOCUMENT, NgComponentOutlet, NgTemplateOutlet, getLocaleDateFormat, FormatWidth, NgForOf, NgClass } from '@angular/common';
5
- import { ClarityIcons, arrowIcon, angleIcon, timesIcon, trashIcon, plusCircleIcon, exclamationCircleIcon, searchIcon, ellipsisVerticalIcon, filterGridCircleIcon, filterGridIcon, pencilIcon, historyIcon as historyIcon$1, treeViewIcon, organizationIcon, calendarIcon, checkCircleIcon, windowCloseIcon, exclamationTriangleIcon, infoStandardIcon, copyToClipboardIcon, successStandardIcon, angleDoubleIcon } from '@cds/core/icon';
4
+ import { CommonModule, DOCUMENT, NgComponentOutlet, NgTemplateOutlet, getLocaleDateFormat, FormatWidth, DecimalPipe, NgForOf, NgClass } from '@angular/common';
5
+ import { ClarityIcons, arrowIcon, angleIcon, timesIcon, trashIcon, plusCircleIcon, exclamationCircleIcon, searchIcon, ellipsisVerticalIcon, filterGridCircleIcon, filterGridIcon, pencilIcon, historyIcon as historyIcon$1, treeViewIcon, organizationIcon, calendarIcon, checkCircleIcon, windowCloseIcon, exclamationTriangleIcon, infoStandardIcon, downloadIcon, copyToClipboardIcon, successStandardIcon, angleDoubleIcon } from '@cds/core/icon';
6
6
  import * as i1$1 from '@clr/angular';
7
- import { ClarityModule, ClrFormsModule, ClrDropdown, ClrIconModule, ClrForm, ClrAlert, ClrAlignment, ClrSide, ClrAxis, ClrPopoverToggleService, ClrPopoverEventsService, ClrPopoverPositionService, ClrPopoverHostDirective, ClrCommonStringsService, ClrDropdownModule, ClrDatagrid, ClrDatagridColumn, ClrDatagridSortOrder, DatagridPropertyComparator, ClrDatagridPagination, ClrDatagridFilter, ClrDateContainer, ClrLabel, ClrControlHelper, ClrControlError, ClrControlSuccess, ClrDatepickerModule, ClrSignpostContent, ClrSignpostModule, ClrTooltipModule } from '@clr/angular';
7
+ import { ClarityModule, ClrFormsModule, ClrDropdown, ClrIconModule, ClrForm, ClrAlert, ClrAlignment, ClrSide, ClrAxis, ClrPopoverToggleService, ClrPopoverEventsService, ClrPopoverPositionService, ClrPopoverHostDirective, ClrCommonStringsService, ClrDropdownModule, ClrDatagrid, ClrDatagridColumn, ClrDatagridSortOrder, DatagridPropertyComparator, ClrDatagridPagination, ClrDatagridFilter, ClrDateContainer, ClrLabel, ClrControlHelper, ClrControlError, ClrControlSuccess, ClrDatepickerModule, ClrSignpostContent, ClrSignpostModule, ClrAlertModule, ClrStartDateInput, ClrEndDateInput, ClrTooltipModule } from '@clr/angular';
8
8
  import * as i3$1 from '@angular/forms';
9
9
  import { FormsModule, NG_VALIDATORS, NgControl, NG_VALUE_ACCESSOR, ReactiveFormsModule, FormControl, NgModel } from '@angular/forms';
10
10
  import { Subject, BehaviorSubject, timer as timer$1, asyncScheduler, interval, map as map$1, distinctUntilChanged, shareReplay, combineLatest, skip, auditTime, fromEvent, take as take$1, merge, debounceTime as debounceTime$1, tap, of, switchMap, filter as filter$2, ReplaySubject, takeUntil as takeUntil$1, delay } from 'rxjs';
11
- import { takeUntil, take, observeOn, filter as filter$1, debounceTime } from 'rxjs/operators';
11
+ import { takeUntil, take, observeOn, filter as filter$1, debounceTime, map as map$2, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs/operators';
12
12
  import * as i3 from '@angular/router';
13
13
  import { RouterModule } from '@angular/router';
14
14
  import { trigger, transition, style, animate, state } from '@angular/animations';
@@ -16,8 +16,10 @@ import { takeUntilDestroyed, toObservable, outputFromObservable, toSignal } from
16
16
  import * as i1$2 from '@angular/common/http';
17
17
  import '@cds/core/icon/register.js';
18
18
  import { CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
19
- import { provideAnimations } from '@angular/platform-browser/animations';
19
+ import { format, select, scaleBand, scaleLinear, max, axisBottom, axisLeft, group, sum, scalePoint, line, curveMonotoneX, area, axisRight, arc, pie, color } from 'd3';
20
+ import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader';
20
21
  import { CdkCopyToClipboard } from '@angular/cdk/clipboard';
22
+ import { provideAnimations } from '@angular/platform-browser/animations';
21
23
 
22
24
  /*
23
25
  * Copyright (c) 2018-2025 Porsche Informatik. All Rights Reserved.
@@ -15483,13 +15485,12 @@ class ClrControlEnterSubmitDirective {
15483
15485
  }
15484
15486
  }
15485
15487
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrControlEnterSubmitDirective, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i1$1.ClrForm, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
15486
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.18", type: ClrControlEnterSubmitDirective, isStandalone: false, selector: "form[clrControlEnterSubmit]", inputs: { tooltipText: ["clrControlEnterSubmit", "tooltipText"] }, host: { listeners: { "window:keydown.control.enter": "submitCtrlEnter($event)" } }, ngImport: i0 }); }
15488
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.18", type: ClrControlEnterSubmitDirective, isStandalone: true, selector: "form[clrControlEnterSubmit]", inputs: { tooltipText: ["clrControlEnterSubmit", "tooltipText"] }, host: { listeners: { "window:keydown.control.enter": "submitCtrlEnter($event)" } }, ngImport: i0 }); }
15487
15489
  }
15488
15490
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrControlEnterSubmitDirective, decorators: [{
15489
15491
  type: Directive,
15490
15492
  args: [{
15491
15493
  selector: 'form[clrControlEnterSubmit]',
15492
- standalone: false,
15493
15494
  }]
15494
15495
  }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i1$1.ClrForm, decorators: [{
15495
15496
  type: Optional
@@ -15607,237 +15608,2719 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
15607
15608
  * This software is released under MIT license.
15608
15609
  * The full license information can be found in LICENSE in the root directory of this project.
15609
15610
  */
15610
- class ClrAddonsModule {
15611
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrAddonsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
15612
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.18", ngImport: i0, type: ClrAddonsModule, declarations: [ClrControlEnterSubmitDirective], imports: [ClrFocusFirstInvalidFieldDirective], exports: [ClrViewEditSectionModule,
15613
- ClrPagerModule,
15614
- ClrDotPagerModule,
15615
- ClrPagedSearchResultListModule,
15616
- ClrCollapseExpandSectionModule,
15617
- ClrBreadcrumbModule,
15618
- ClrMainNavGroupModule,
15619
- ClrContentPanelModule,
15620
- ClrNotificationModule,
15621
- ClrFlowBarModule,
15622
- ClrBackButtonModule,
15623
- ClrNumericFieldModule,
15624
- ClrSearchFieldModule,
15625
- ClrTreetableModule,
15626
- ClrProgressSpinnerModule,
15627
- ClrDateTimeModule,
15628
- ClrQuickListModule,
15629
- ClrIconAvatarModule,
15630
- ClrLetterAvatarModule,
15631
- ClrMultilingualModule,
15632
- ClrGenericQuickListModule,
15633
- ClrDataListValidatorModule,
15634
- ClrHistoryModule,
15635
- ClrAutocompleteOffModule,
15636
- ClrBrandAvatarModule,
15637
- ClrLocationBarModule,
15638
- ClrFormModule,
15639
- ClrDropdownOverflowModule,
15640
- ClrDatagridStatePersistenceModule,
15641
- ClrEnumFilterModule,
15642
- ClrDateFilterModule,
15643
- ClrDaterangepickerModule,
15644
- ClrIfWarningModule,
15645
- ClrActionPanelModule,
15646
- ClrReadonlyDirectiveModule,
15647
- ClrDatagridColumnReorderModule,
15648
- ClrSignpostAddonModule,
15649
- ClrFocusFirstInvalidFieldDirective,
15650
- ClrControlEnterSubmitDirective] }); }
15651
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrAddonsModule, imports: [ClrViewEditSectionModule,
15652
- ClrPagerModule,
15653
- ClrDotPagerModule,
15654
- ClrPagedSearchResultListModule,
15655
- ClrCollapseExpandSectionModule,
15656
- ClrBreadcrumbModule,
15657
- ClrMainNavGroupModule,
15658
- ClrContentPanelModule,
15659
- ClrNotificationModule,
15660
- ClrFlowBarModule,
15661
- ClrBackButtonModule,
15662
- ClrNumericFieldModule,
15663
- ClrSearchFieldModule,
15664
- ClrTreetableModule,
15665
- ClrProgressSpinnerModule,
15666
- ClrDateTimeModule,
15667
- ClrQuickListModule,
15668
- ClrIconAvatarModule,
15669
- ClrLetterAvatarModule,
15670
- ClrMultilingualModule,
15671
- ClrGenericQuickListModule,
15672
- ClrDataListValidatorModule,
15673
- ClrHistoryModule,
15674
- ClrAutocompleteOffModule,
15675
- ClrBrandAvatarModule,
15676
- ClrLocationBarModule,
15677
- ClrFormModule,
15678
- ClrDropdownOverflowModule,
15679
- ClrDatagridStatePersistenceModule,
15680
- ClrEnumFilterModule,
15681
- ClrDateFilterModule,
15682
- ClrDaterangepickerModule,
15683
- ClrIfWarningModule,
15684
- ClrActionPanelModule,
15685
- ClrReadonlyDirectiveModule,
15686
- ClrDatagridColumnReorderModule,
15687
- ClrSignpostAddonModule] }); }
15611
+
15612
+ const TOO_MANY_ITEMS_MESSAGE = 'too many items';
15613
+ const TOO_MANY_ITEMS_GROUPED_MESSAGE = 'too many items';
15614
+ const TOO_MANY_ITEMS_ALERT_TYPE = 'warning';
15615
+ const NO_ITEMS_MESSAGE = 'no items';
15616
+ const NO_ITEMS_ALERT_TYPE = 'info';
15617
+
15618
+ /**
15619
+ * Converts a chart color value to a CSS-compatible string.
15620
+ *
15621
+ * Supports:
15622
+ * - Hex values: '#e57200' → returned as-is
15623
+ * - CSS custom props: '--cds-global-color-lavender-1000' → wrapped in var(...)
15624
+ * - Any other string: 'rgb(...)' / 'hsl(...)' → returned as-is
15625
+ */
15626
+ function toChartColor(color) {
15627
+ if (!color) {
15628
+ return '';
15629
+ }
15630
+ return color.startsWith('--') ? `var(${color})` : color;
15631
+ }
15632
+
15633
+ class TextRenderer {
15634
+ constructor() {
15635
+ this.cachedFont = '';
15636
+ this.cachedEllipsisWidth = 0;
15637
+ this.canvas = document.createElement('canvas');
15638
+ const ctx = this.canvas.getContext('2d');
15639
+ if (!ctx) {
15640
+ throw new Error('2D context error');
15641
+ }
15642
+ this.context = ctx;
15643
+ }
15644
+ render(text, availableHeight, availableWidth, fontSize = '12px', fontFamily = 'Arial') {
15645
+ const font = `${fontSize} ${fontFamily}`;
15646
+ if (this.cachedFont !== font) {
15647
+ this.context.font = font;
15648
+ this.cachedEllipsisWidth = this.context.measureText('...').width;
15649
+ this.cachedFont = font;
15650
+ }
15651
+ else {
15652
+ this.context.font = font;
15653
+ }
15654
+ const textMetrics = this.context.measureText(text);
15655
+ const textWidth = textMetrics.width;
15656
+ const textHeight = Math.abs(textMetrics.actualBoundingBoxAscent) + Math.abs(textMetrics.actualBoundingBoxDescent);
15657
+ if (textWidth <= availableWidth && textHeight <= availableHeight) {
15658
+ return text;
15659
+ }
15660
+ let remainingWidth = availableWidth - this.cachedEllipsisWidth;
15661
+ let truncatedText = '';
15662
+ for (const char of text) {
15663
+ const charWidth = this.context.measureText(char).width;
15664
+ if (remainingWidth - charWidth >= 0) {
15665
+ truncatedText += char;
15666
+ remainingWidth -= charWidth;
15667
+ }
15668
+ else {
15669
+ break;
15670
+ }
15671
+ }
15672
+ truncatedText = truncatedText.trim();
15673
+ if (truncatedText.length === 0) {
15674
+ return '';
15675
+ }
15676
+ return truncatedText + '...';
15677
+ }
15688
15678
  }
15689
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrAddonsModule, decorators: [{
15690
- type: NgModule,
15691
- args: [{
15692
- imports: [ClrFocusFirstInvalidFieldDirective],
15693
- declarations: [ClrControlEnterSubmitDirective],
15694
- exports: [
15695
- ClrViewEditSectionModule,
15696
- ClrPagerModule,
15697
- ClrDotPagerModule,
15698
- ClrPagedSearchResultListModule,
15699
- ClrCollapseExpandSectionModule,
15700
- ClrBreadcrumbModule,
15701
- ClrMainNavGroupModule,
15702
- ClrContentPanelModule,
15703
- ClrNotificationModule,
15704
- ClrFlowBarModule,
15705
- ClrBackButtonModule,
15706
- ClrNumericFieldModule,
15707
- ClrSearchFieldModule,
15708
- ClrTreetableModule,
15709
- ClrProgressSpinnerModule,
15710
- ClrDateTimeModule,
15711
- ClrQuickListModule,
15712
- ClrIconAvatarModule,
15713
- ClrLetterAvatarModule,
15714
- ClrMultilingualModule,
15715
- ClrGenericQuickListModule,
15716
- ClrDataListValidatorModule,
15717
- ClrHistoryModule,
15718
- ClrAutocompleteOffModule,
15719
- ClrBrandAvatarModule,
15720
- ClrLocationBarModule,
15721
- ClrFormModule,
15722
- ClrDropdownOverflowModule,
15723
- ClrDatagridStatePersistenceModule,
15724
- ClrEnumFilterModule,
15725
- ClrDateFilterModule,
15726
- ClrDaterangepickerModule,
15727
- ClrIfWarningModule,
15728
- ClrActionPanelModule,
15729
- ClrReadonlyDirectiveModule,
15730
- ClrDatagridColumnReorderModule,
15731
- ClrSignpostAddonModule,
15732
- ClrFocusFirstInvalidFieldDirective,
15733
- ClrControlEnterSubmitDirective,
15734
- ],
15735
- }]
15736
- }] });
15679
+
15680
+ const percentage = (value, total) => {
15681
+ if (total <= 0) {
15682
+ return 0;
15683
+ }
15684
+ return Math.min(100, Math.max(0, Math.round((value / total) * 100)));
15685
+ };
15686
+ const d3percentFormat = (f => (d) => `${f(d)}%`)(format('.1f'));
15737
15687
 
15738
15688
  /*
15739
- * Copyright (c) 2018-2024 Porsche Informatik. All Rights Reserved.
15689
+ * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
15740
15690
  * This software is released under MIT license.
15741
15691
  * The full license information can be found in LICENSE in the root directory of this project.
15742
15692
  */
15743
-
15744
- var ExportTypeEnum;
15745
- (function (ExportTypeEnum) {
15746
- ExportTypeEnum["ALL"] = "ALL";
15747
- ExportTypeEnum["FILTERED"] = "FILTERED";
15748
- ExportTypeEnum["SELECTED"] = "SELECTED";
15749
- })(ExportTypeEnum || (ExportTypeEnum = {}));
15750
-
15751
- class ExportDatagridService {
15693
+ /**
15694
+ * Abstract base class shared by all chart components.
15695
+ * ...
15696
+ */
15697
+ class ChartBase {
15752
15698
  constructor() {
15753
- this.init();
15699
+ // ── Common Input ───────────────────────────────────────────────────────────
15700
+ /** Whether the chart is in loading state (shows skeleton). */
15701
+ this.loading = input(false);
15702
+ // ── View references ────────────────────────────────────────────────────────
15703
+ /** Reference to the `<svg #chart>` element in the component template. */
15704
+ this.chartRef = viewChild('chart');
15705
+ /** Reference to the `<div #container>` host element in the component template. */
15706
+ this.containerRef = viewChild('container');
15707
+ /** Exposes the raw SVG element for the chart-export button. */
15708
+ this.svgElement = computed(() => this.chartRef()?.nativeElement);
15709
+ // ── Tooltip State ──────────────────────────────────────────────────────────
15710
+ this.selectedItem = signal(undefined);
15711
+ this.tooltipPosition = signal(undefined);
15712
+ }
15713
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
15714
+ /** Schedules the first render after the view is ready. */
15715
+ ngAfterViewInit() {
15716
+ requestAnimationFrame(() => this.updateChart());
15754
15717
  }
15755
- async init() {
15756
- try {
15757
- this.zipcelx = await import('zipcelx');
15758
- }
15759
- catch (error) {
15760
- console.warn('Optional feature requires `zipcelx`. Please install it: npm install zipcelx', error);
15761
- }
15718
+ // ── Common Implementations ─────────────────────────────────────────────────
15719
+ /** Returns the pixel dimensions of the container div. */
15720
+ getContainerDimensions() {
15721
+ const el = this.containerRef().nativeElement;
15722
+ return { width: el.clientWidth, height: el.clientHeight };
15762
15723
  }
15763
- exportToExcel(filename, headers, rows) {
15764
- const config = {
15765
- filename,
15766
- sheet: {
15767
- data: [headers, ...rows],
15768
- },
15769
- };
15770
- this.zipcelx.default(config);
15724
+ /**
15725
+ * Clears the active tooltip. Override in subclasses that have additional
15726
+ * selection state
15727
+ */
15728
+ resetTooltip() {
15729
+ if (this.selectedItem() || this.tooltipPosition()) {
15730
+ this.selectedItem.set(undefined);
15731
+ this.tooltipPosition.set(undefined);
15732
+ }
15771
15733
  }
15772
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ExportDatagridService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
15773
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ExportDatagridService }); }
15734
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
15735
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "19.2.18", type: ChartBase, isStandalone: true, inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "chartRef", first: true, predicate: ["chart"], descendants: true, isSignal: true }, { propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, isSignal: true }], ngImport: i0 }); }
15774
15736
  }
15775
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ExportDatagridService, decorators: [{
15776
- type: Injectable
15777
- }], ctorParameters: () => [] });
15737
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartBase, decorators: [{
15738
+ type: Directive
15739
+ }] });
15778
15740
 
15779
- class ExportDatagridButtonComponent {
15780
- constructor(exportService) {
15781
- this.exportService = exportService;
15782
- /* input signals */
15783
- this.datagrid = input();
15784
- this.datagridRef = input();
15785
- this.exportTypesToShow = input();
15786
- this.isBackendExport = input(false);
15787
- this.exportTitlePrefix = input('export-datagrid');
15788
- this.exportButtonPosition = input('right');
15789
- this.possibleExportTypes = signal([ExportTypeEnum.ALL]);
15790
- this.exportButtonText = input('EXPORT');
15791
- this.destroy$ = new Subject();
15792
- /* outputs */
15793
- this.backendExport = new EventEmitter();
15794
- this.exportTypes = [
15795
- { type: ExportTypeEnum.ALL, value: 'All entries' },
15796
- { type: ExportTypeEnum.FILTERED, value: 'Filtered entries' },
15797
- { type: ExportTypeEnum.SELECTED, value: 'Selected entries' },
15798
- ];
15799
- this.exportTypesFiltered = computed(() => {
15800
- let exportTypesToShowVal = this.exportTypesToShow();
15801
- if (!exportTypesToShowVal || exportTypesToShowVal.length === 0) {
15802
- exportTypesToShowVal = this.exportTypes;
15803
- }
15804
- return exportTypesToShowVal
15805
- .filter(showType => this.possibleExportTypes().some(et => et === showType.type))
15806
- .map(showType => {
15807
- const defaultType = this.exportTypes.find(et => et.type === showType.type);
15808
- return {
15809
- type: showType.type,
15810
- value: showType.value != null ? showType.value : defaultType?.value,
15811
- };
15812
- });
15813
- });
15814
- effect(() => {
15815
- const datagrid = this.datagrid();
15816
- if (!datagrid) {
15817
- return undefined;
15818
- }
15819
- this.destroy$.next();
15820
- datagrid.refresh.pipe(delay(0), takeUntil(this.destroy$)).subscribe(dgState => {
15821
- const hasFilter = dgState.filters && dgState.filters.length > 0;
15822
- this.updateExportType(ExportTypeEnum.FILTERED, hasFilter, this.exportTypesToShow() || this.exportTypes);
15823
- });
15824
- datagrid.selectedChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
15825
- const hasSelection = datagrid.selection.current.length > 0;
15826
- this.updateExportType(ExportTypeEnum.SELECTED, hasSelection, this.exportTypesToShow() || this.exportTypes);
15827
- });
15741
+ class ChartAlertOverlayComponent {
15742
+ constructor() {
15743
+ this.alertMessage = input.required();
15744
+ this.alertType = input('info');
15745
+ }
15746
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartAlertOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15747
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.18", type: ChartAlertOverlayComponent, isStandalone: true, selector: "cng-chart-alert-overlay", inputs: { alertMessage: { classPropertyName: "alertMessage", publicName: "alertMessage", isSignal: true, isRequired: true, transformFunction: null }, alertType: { classPropertyName: "alertType", publicName: "alertType", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
15748
+ <clr-alert [clrAlertSizeSmall]="true" [clrAlertClosable]="false" [clrAlertType]="alertType()">
15749
+ <clr-alert-item>
15750
+ <span class="alert-text">
15751
+ {{ alertMessage() }}
15752
+ </span>
15753
+ </clr-alert-item>
15754
+ </clr-alert>
15755
+ <div class="overlay"></div>
15756
+ `, isInline: true, styles: [":host{position:absolute;top:0;left:0;right:0;pointer-events:none;z-index:1;clr-alert{pointer-events:auto;::ng-deep .alert{margin-bottom:0}}.overlay{height:2rem;background:linear-gradient(to bottom,#fff,#fff0)}}\n"], dependencies: [{ kind: "ngmodule", type: ClrAlertModule }, { kind: "component", type: i1$1.ClrAlert, selector: "clr-alert", inputs: ["clrAlertSizeSmall", "clrAlertClosable", "clrAlertAppLevel", "clrCloseButtonAriaLabel", "clrAlertLightweight", "clrAlertType", "clrAlertIcon", "clrAlertClosed"], outputs: ["clrAlertClosedChange"] }, { kind: "component", type: i1$1.ClrAlertItem, selector: "clr-alert-item" }, { kind: "directive", type: i1$1.ClrAlertText, selector: ".alert-text" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15757
+ }
15758
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartAlertOverlayComponent, decorators: [{
15759
+ type: Component,
15760
+ args: [{ selector: 'cng-chart-alert-overlay', template: `
15761
+ <clr-alert [clrAlertSizeSmall]="true" [clrAlertClosable]="false" [clrAlertType]="alertType()">
15762
+ <clr-alert-item>
15763
+ <span class="alert-text">
15764
+ {{ alertMessage() }}
15765
+ </span>
15766
+ </clr-alert-item>
15767
+ </clr-alert>
15768
+ <div class="overlay"></div>
15769
+ `, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ClrAlertModule], styles: [":host{position:absolute;top:0;left:0;right:0;pointer-events:none;z-index:1;clr-alert{pointer-events:auto;::ng-deep .alert{margin-bottom:0}}.overlay{height:2rem;background:linear-gradient(to bottom,#fff,#fff0)}}\n"] }]
15770
+ }] });
15771
+
15772
+ class ChartExportService {
15773
+ exportSvg(svgEl, filename) {
15774
+ const clone = this.cloneWithDimensions(svgEl);
15775
+ const svgStr = new XMLSerializer().serializeToString(clone);
15776
+ this.download(new Blob([svgStr], { type: 'image/svg+xml;charset=utf-8' }), `${filename}.svg`);
15777
+ }
15778
+ exportPng(svgEl, filename) {
15779
+ this.toCanvas(svgEl).then(canvas => canvas.toBlob(blob => this.download(blob, `${filename}.png`), 'image/png'));
15780
+ }
15781
+ toCanvas(svgEl) {
15782
+ return new Promise(resolve => {
15783
+ const w = svgEl.clientWidth || Number(svgEl.getAttribute('width')) || 800;
15784
+ const h = svgEl.clientHeight || Number(svgEl.getAttribute('height')) || 600;
15785
+ const clone = this.cloneWithDimensions(svgEl, w, h);
15786
+ const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone)], { type: 'image/svg+xml;charset=utf-8' }));
15787
+ const img = new Image();
15788
+ img.onload = () => {
15789
+ const scale = 2; // 2× for retina quality
15790
+ const canvas = document.createElement('canvas');
15791
+ canvas.width = w * scale;
15792
+ canvas.height = h * scale;
15793
+ const ctx = canvas.getContext('2d');
15794
+ ctx.scale(scale, scale);
15795
+ ctx.fillStyle = '#ffffff';
15796
+ ctx.fillRect(0, 0, w, h);
15797
+ ctx.drawImage(img, 0, 0, w, h);
15798
+ URL.revokeObjectURL(url);
15799
+ resolve(canvas);
15800
+ };
15801
+ img.src = url;
15828
15802
  });
15829
15803
  }
15830
- ngOnDestroy() {
15831
- this.destroy$.next();
15832
- this.destroy$.complete();
15804
+ cloneWithDimensions(svgEl, w, h) {
15805
+ const clone = svgEl.cloneNode(true);
15806
+ const width = w ?? svgEl.clientWidth ?? Number(svgEl.getAttribute('width')) ?? 800;
15807
+ const height = h ?? svgEl.clientHeight ?? Number(svgEl.getAttribute('height')) ?? 600;
15808
+ clone.setAttribute('width', String(width));
15809
+ clone.setAttribute('height', String(height));
15810
+ clone.style.background = '#ffffff';
15811
+ return clone;
15812
+ }
15813
+ download(blob, filename) {
15814
+ const url = URL.createObjectURL(blob);
15815
+ const a = document.createElement('a');
15816
+ a.href = url;
15817
+ a.download = filename;
15818
+ a.click();
15819
+ setTimeout(() => URL.revokeObjectURL(url), 100);
15820
+ }
15821
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartExportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
15822
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartExportService, providedIn: 'root' }); }
15823
+ }
15824
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartExportService, decorators: [{
15825
+ type: Injectable,
15826
+ args: [{ providedIn: 'root' }]
15827
+ }] });
15828
+
15829
+ ClarityIcons.addIcons(downloadIcon);
15830
+ class ChartExportButtonComponent {
15831
+ constructor() {
15832
+ this.svgRef = input(undefined);
15833
+ this.filename = input('chart');
15834
+ this.exportService = inject(ChartExportService);
15833
15835
  }
15834
- exportExcel(type) {
15835
- if (!this.datagrid() || !this.datagridRef()) {
15836
+ export(format) {
15837
+ const svg = this.svgRef();
15838
+ if (!svg) {
15836
15839
  return;
15837
15840
  }
15838
- // Filter visible columns once
15839
- const visibleColumns = this.datagrid().columns.filter(col => !col.isHidden);
15840
- // Get header titles for visible columns only
15841
+ switch (format) {
15842
+ case 'svg':
15843
+ this.exportService.exportSvg(svg, this.filename());
15844
+ break;
15845
+ case 'png':
15846
+ this.exportService.exportPng(svg, this.filename());
15847
+ break;
15848
+ }
15849
+ }
15850
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartExportButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15851
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.18", type: ChartExportButtonComponent, isStandalone: true, selector: "cng-chart-export-button", inputs: { svgRef: { classPropertyName: "svgRef", publicName: "svgRef", isSignal: true, isRequired: false, transformFunction: null }, filename: { classPropertyName: "filename", publicName: "filename", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
15852
+ <clr-dropdown>
15853
+ <button class="btn btn-sm btn-icon btn-link export-trigger" clrDropdownTrigger title="Export chart">
15854
+ <cds-icon shape="download" size="16"></cds-icon>
15855
+ </button>
15856
+ <clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
15857
+ <button clrDropdownItem (click)="export('svg')">SVG</button>
15858
+ <button clrDropdownItem (click)="export('png')">PNG</button>
15859
+ </clr-dropdown-menu>
15860
+ </clr-dropdown>
15861
+ `, isInline: true, styles: [":host{position:absolute;bottom:-15px;left:-5px;z-index:10;opacity:1;pointer-events:auto}\n"], dependencies: [{ kind: "ngmodule", type: ClrDropdownModule }, { kind: "component", type: i1$1.ClrDropdown, selector: "clr-dropdown", inputs: ["clrCloseMenuOnItemClick"] }, { kind: "component", type: i1$1.ClrDropdownMenu, selector: "clr-dropdown-menu", inputs: ["clrPosition"] }, { kind: "directive", type: i1$1.ClrDropdownTrigger, selector: "[clrDropdownTrigger],[clrDropdownToggle]" }, { kind: "directive", type: i1$1.ClrDropdownItem, selector: "[clrDropdownItem]", inputs: ["clrDisabled", "id"] }, { kind: "directive", type: i1$1.ClrIfOpen, selector: "[clrIfOpen]", inputs: ["clrIfOpen"], outputs: ["clrIfOpenChange"] }, { kind: "directive", type: i1$1.CdsIconCustomTag, selector: "cds-icon" }, { kind: "ngmodule", type: ClrIconModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15862
+ }
15863
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartExportButtonComponent, decorators: [{
15864
+ type: Component,
15865
+ args: [{ selector: 'cng-chart-export-button', template: `
15866
+ <clr-dropdown>
15867
+ <button class="btn btn-sm btn-icon btn-link export-trigger" clrDropdownTrigger title="Export chart">
15868
+ <cds-icon shape="download" size="16"></cds-icon>
15869
+ </button>
15870
+ <clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
15871
+ <button clrDropdownItem (click)="export('svg')">SVG</button>
15872
+ <button clrDropdownItem (click)="export('png')">PNG</button>
15873
+ </clr-dropdown-menu>
15874
+ </clr-dropdown>
15875
+ `, imports: [ClrDropdownModule, ClrIconModule], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{position:absolute;bottom:-15px;left:-5px;z-index:10;opacity:1;pointer-events:auto}\n"] }]
15876
+ }] });
15877
+
15878
+ class ChartLegendComponent {
15879
+ constructor() {
15880
+ this.items = input.required();
15881
+ this.toChartColor = toChartColor;
15882
+ }
15883
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartLegendComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15884
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ChartLegendComponent, isStandalone: true, selector: "cng-chart-legend", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
15885
+ <div class="chart-legend">
15886
+ @for (item of items(); track item.label) {
15887
+ <div class="legend-item">
15888
+ <span class="legend-color-square" [style.background-color]="toChartColor(item.color)"></span>
15889
+ <span class="legend-label">{{ item.label }}</span>
15890
+ </div>
15891
+ }
15892
+ </div>
15893
+ `, isInline: true, styles: [":host{display:block}.chart-legend{display:flex;flex-wrap:wrap;gap:.25rem 1rem;padding:.5rem 0 .25rem;font-size:11px;color:var(--clr-color-neutral-600, #666)}.legend-item{display:flex;align-items:center;gap:.35rem}.legend-color-square{width:10px;height:10px;border-radius:2px;flex-shrink:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15894
+ }
15895
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartLegendComponent, decorators: [{
15896
+ type: Component,
15897
+ args: [{ selector: 'cng-chart-legend', template: `
15898
+ <div class="chart-legend">
15899
+ @for (item of items(); track item.label) {
15900
+ <div class="legend-item">
15901
+ <span class="legend-color-square" [style.background-color]="toChartColor(item.color)"></span>
15902
+ <span class="legend-label">{{ item.label }}</span>
15903
+ </div>
15904
+ }
15905
+ </div>
15906
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}.chart-legend{display:flex;flex-wrap:wrap;gap:.25rem 1rem;padding:.5rem 0 .25rem;font-size:11px;color:var(--clr-color-neutral-600, #666)}.legend-item{display:flex;align-items:center;gap:.35rem}.legend-color-square{width:10px;height:10px;border-radius:2px;flex-shrink:0}\n"] }]
15907
+ }] });
15908
+
15909
+ class ChartSkeletonComponent {
15910
+ constructor() {
15911
+ this.skeletonType = input('loading');
15912
+ this.orientation = input.required();
15913
+ this.animationStyle = computed(() => (this.skeletonType() === 'loading' ? 'pulse' : undefined));
15914
+ }
15915
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartSkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15916
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ChartSkeletonComponent, isStandalone: true, selector: "cng-bar-chart-skeleton", inputs: { skeletonType: { classPropertyName: "skeletonType", publicName: "skeletonType", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"chart-skeleton\" [class.placeholder-skeleton]=\"skeletonType() === 'placeholder'\">\n <div\n class=\"skeleton-bars\"\n [class.horizontal]=\"orientation() === 'horizontal'\"\n [class.vertical]=\"orientation() === 'vertical'\"\n >\n @for (_item of [1, 2, 3, 4, 5, 6]; track $index) {\n <div class=\"skeleton-bar bar-{{ $index + 1 }}\">\n <ngx-skeleton-loader\n [appearance]=\"'circle'\"\n [count]=\"1\"\n [animation]=\"animationStyle()\"\n [theme]=\"{\n width: '100%',\n height: '100%',\n 'border-radius': 'var(--clr-base-border-radius-xxs)',\n }\"\n />\n </div>\n }\n </div>\n</div>\n", styles: [".chart-skeleton{width:100%;height:100%}.placeholder-skeleton ::ng-deep .skeleton-loader{cursor:default}.skeleton-bars{display:flex;gap:var(--clr-base-gap-l);width:100%;height:100%;padding:var(--cds-global-layout-space-xs) var(--cds-global-layout-space-xl)}.skeleton-bars.vertical{flex-direction:row;align-items:flex-end;justify-content:center}.skeleton-bars.vertical .skeleton-bar{width:var(--cds-global-layout-space-md)}.skeleton-bars.vertical .skeleton-bar.bar-1{height:60%}.skeleton-bars.vertical .skeleton-bar.bar-2{height:95%}.skeleton-bars.vertical .skeleton-bar.bar-3{height:40%}.skeleton-bars.vertical .skeleton-bar.bar-4{height:75%}.skeleton-bars.vertical .skeleton-bar.bar-5{height:50%}.skeleton-bars.vertical .skeleton-bar.bar-6{height:25%}.skeleton-bars.horizontal{flex-direction:column;justify-content:center}.skeleton-bars.horizontal .skeleton-bar{height:var(--cds-global-layout-space-md)}.skeleton-bars.horizontal .skeleton-bar.bar-1{width:60%}.skeleton-bars.horizontal .skeleton-bar.bar-2{width:95%}.skeleton-bars.horizontal .skeleton-bar.bar-3{width:40%}.skeleton-bars.horizontal .skeleton-bar.bar-4{width:75%}.skeleton-bars.horizontal .skeleton-bar.bar-5{width:50%}.skeleton-bars.horizontal .skeleton-bar.bar-6{width:25%}\n"], dependencies: [{ kind: "component", type: NgxSkeletonLoaderComponent, selector: "ngx-skeleton-loader", inputs: ["count", "loadingText", "appearance", "animation", "ariaLabel", "theme"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15917
+ }
15918
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartSkeletonComponent, decorators: [{
15919
+ type: Component,
15920
+ args: [{ selector: 'cng-bar-chart-skeleton', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxSkeletonLoaderComponent], template: "<div class=\"chart-skeleton\" [class.placeholder-skeleton]=\"skeletonType() === 'placeholder'\">\n <div\n class=\"skeleton-bars\"\n [class.horizontal]=\"orientation() === 'horizontal'\"\n [class.vertical]=\"orientation() === 'vertical'\"\n >\n @for (_item of [1, 2, 3, 4, 5, 6]; track $index) {\n <div class=\"skeleton-bar bar-{{ $index + 1 }}\">\n <ngx-skeleton-loader\n [appearance]=\"'circle'\"\n [count]=\"1\"\n [animation]=\"animationStyle()\"\n [theme]=\"{\n width: '100%',\n height: '100%',\n 'border-radius': 'var(--clr-base-border-radius-xxs)',\n }\"\n />\n </div>\n }\n </div>\n</div>\n", styles: [".chart-skeleton{width:100%;height:100%}.placeholder-skeleton ::ng-deep .skeleton-loader{cursor:default}.skeleton-bars{display:flex;gap:var(--clr-base-gap-l);width:100%;height:100%;padding:var(--cds-global-layout-space-xs) var(--cds-global-layout-space-xl)}.skeleton-bars.vertical{flex-direction:row;align-items:flex-end;justify-content:center}.skeleton-bars.vertical .skeleton-bar{width:var(--cds-global-layout-space-md)}.skeleton-bars.vertical .skeleton-bar.bar-1{height:60%}.skeleton-bars.vertical .skeleton-bar.bar-2{height:95%}.skeleton-bars.vertical .skeleton-bar.bar-3{height:40%}.skeleton-bars.vertical .skeleton-bar.bar-4{height:75%}.skeleton-bars.vertical .skeleton-bar.bar-5{height:50%}.skeleton-bars.vertical .skeleton-bar.bar-6{height:25%}.skeleton-bars.horizontal{flex-direction:column;justify-content:center}.skeleton-bars.horizontal .skeleton-bar{height:var(--cds-global-layout-space-md)}.skeleton-bars.horizontal .skeleton-bar.bar-1{width:60%}.skeleton-bars.horizontal .skeleton-bar.bar-2{width:95%}.skeleton-bars.horizontal .skeleton-bar.bar-3{width:40%}.skeleton-bars.horizontal .skeleton-bar.bar-4{width:75%}.skeleton-bars.horizontal .skeleton-bar.bar-5{width:50%}.skeleton-bars.horizontal .skeleton-bar.bar-6{width:25%}\n"] }]
15921
+ }] });
15922
+
15923
+ class ChartTooltipComponent {
15924
+ constructor() {
15925
+ this.tooltipPosition = input.required();
15926
+ this.tooltipOrientation = input('top');
15927
+ this.squareColor = input.required();
15928
+ this.tooltipClickable = input(true);
15929
+ this.tooltipClosed = output();
15930
+ this.tooltipHeaderClicked = output();
15931
+ this.toChartColor = toChartColor;
15932
+ }
15933
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15934
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ChartTooltipComponent, isStandalone: true, selector: "cng-chart-tooltip", inputs: { tooltipPosition: { classPropertyName: "tooltipPosition", publicName: "tooltipPosition", isSignal: true, isRequired: true, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, squareColor: { classPropertyName: "squareColor", publicName: "squareColor", isSignal: true, isRequired: true, transformFunction: null }, tooltipClickable: { classPropertyName: "tooltipClickable", publicName: "tooltipClickable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tooltipClosed: "tooltipClosed", tooltipHeaderClicked: "tooltipHeaderClicked" }, ngImport: i0, template: "<div\n class=\"tooltip-container\"\n [class.orientation-bottom]=\"tooltipOrientation() === 'bottom'\"\n (click)=\"$event.stopPropagation()\"\n [style.left.px]=\"tooltipPosition().x\"\n [style.top.px]=\"tooltipPosition().y\"\n>\n <div class=\"tooltip-header\">\n <div class=\"color-label-wrapper\">\n @if (squareColor()) {\n <div class=\"color-square\" [style.background-color]=\"toChartColor(squareColor())\"></div>\n }\n <h5\n [class.has-more-info]=\"tooltipClickable()\"\n (click)=\"$event.stopPropagation(); tooltipClickable() && tooltipHeaderClicked.emit()\"\n >\n <ng-content select=\"cng-title\" />\n </h5>\n </div>\n <cds-icon shape=\"times\" class=\"tooltip-close-btn\" (click)=\"$event.stopPropagation(); tooltipClosed.emit()\" />\n </div>\n <div class=\"tooltip-content\">\n <ng-content />\n </div>\n <hr />\n <div class=\"tooltip-footer\"><ng-content select=\"cng-footer\" /></div>\n</div>\n", styles: [".tooltip-container{position:absolute;background:var(--clr-color-neutral-0, #fff);border:1px solid var(--clr-color-neutral-400, #ccc);border-radius:4px;box-shadow:0 2px 8px #00000026;padding:12px 16px;z-index:1000;min-width:150px;transform:translate(-50%,-100%) translateY(-10px)}.tooltip-container.orientation-bottom{transform:translate(-50%) translateY(10px)}.tooltip-header{display:flex;flex-direction:row;justify-content:space-between;gap:8px}.tooltip-header h5{margin:0;font-size:14px;font-weight:600;color:var(--clr-color-neutral-1000, #000);flex-grow:1;max-width:200px}.tooltip-close-btn{cursor:pointer;color:var(--clr-close-color)}.tooltip-close-btn:hover,.tooltip-close-btn:focus{color:var(--clr-close-color-hover)}.color-label-wrapper{display:flex;flex-direction:row;gap:8px}.tooltip-content{font-size:12px;color:var(--clr-color-neutral-700, #565656)}hr:has(+.tooltip-footer:empty){display:none}hr{margin:6px -16px}\n"], dependencies: [{ kind: "ngmodule", type: ClrIconModule }, { kind: "directive", type: i1$1.CdsIconCustomTag, selector: "cds-icon" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15935
+ }
15936
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartTooltipComponent, decorators: [{
15937
+ type: Component,
15938
+ args: [{ selector: 'cng-chart-tooltip', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ClrIconModule], template: "<div\n class=\"tooltip-container\"\n [class.orientation-bottom]=\"tooltipOrientation() === 'bottom'\"\n (click)=\"$event.stopPropagation()\"\n [style.left.px]=\"tooltipPosition().x\"\n [style.top.px]=\"tooltipPosition().y\"\n>\n <div class=\"tooltip-header\">\n <div class=\"color-label-wrapper\">\n @if (squareColor()) {\n <div class=\"color-square\" [style.background-color]=\"toChartColor(squareColor())\"></div>\n }\n <h5\n [class.has-more-info]=\"tooltipClickable()\"\n (click)=\"$event.stopPropagation(); tooltipClickable() && tooltipHeaderClicked.emit()\"\n >\n <ng-content select=\"cng-title\" />\n </h5>\n </div>\n <cds-icon shape=\"times\" class=\"tooltip-close-btn\" (click)=\"$event.stopPropagation(); tooltipClosed.emit()\" />\n </div>\n <div class=\"tooltip-content\">\n <ng-content />\n </div>\n <hr />\n <div class=\"tooltip-footer\"><ng-content select=\"cng-footer\" /></div>\n</div>\n", styles: [".tooltip-container{position:absolute;background:var(--clr-color-neutral-0, #fff);border:1px solid var(--clr-color-neutral-400, #ccc);border-radius:4px;box-shadow:0 2px 8px #00000026;padding:12px 16px;z-index:1000;min-width:150px;transform:translate(-50%,-100%) translateY(-10px)}.tooltip-container.orientation-bottom{transform:translate(-50%) translateY(10px)}.tooltip-header{display:flex;flex-direction:row;justify-content:space-between;gap:8px}.tooltip-header h5{margin:0;font-size:14px;font-weight:600;color:var(--clr-color-neutral-1000, #000);flex-grow:1;max-width:200px}.tooltip-close-btn{cursor:pointer;color:var(--clr-close-color)}.tooltip-close-btn:hover,.tooltip-close-btn:focus{color:var(--clr-close-color-hover)}.color-label-wrapper{display:flex;flex-direction:row;gap:8px}.tooltip-content{font-size:12px;color:var(--clr-color-neutral-700, #565656)}hr:has(+.tooltip-footer:empty){display:none}hr{margin:6px -16px}\n"] }]
15939
+ }] });
15940
+
15941
+ // Clarity actually has an "outside-click" directive, but they don't export it 🤷
15942
+ class OutsideClickDirective {
15943
+ constructor() {
15944
+ this.outsideClick = output({ alias: 'cngOutsideClick' });
15945
+ this.renderer = inject(Renderer2);
15946
+ this.destroyRef = inject(DestroyRef);
15947
+ }
15948
+ ngAfterViewInit() {
15949
+ const listener = this.renderer.listen('document', 'click', () => this.outsideClick.emit());
15950
+ this.destroyRef.onDestroy(listener);
15951
+ }
15952
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: OutsideClickDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
15953
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.18", type: OutsideClickDirective, isStandalone: true, selector: "[cngOutsideClick]", outputs: { outsideClick: "cngOutsideClick" }, ngImport: i0 }); }
15954
+ }
15955
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: OutsideClickDirective, decorators: [{
15956
+ type: Directive,
15957
+ args: [{
15958
+ selector: '[cngOutsideClick]',
15959
+ }]
15960
+ }] });
15961
+
15962
+ /**
15963
+ * Enum for the Screen size in pixels, defined by clarity here https://vmware.github.io/clarity/documentation/v0.12/grid
15964
+ * We consider screens above 1200px to be a desktop, between 576px and 1199px to be a tablet and below 576px to be a phone.
15965
+ */
15966
+ var ScreenWidth;
15967
+ (function (ScreenWidth) {
15968
+ ScreenWidth[ScreenWidth["EXTRA_LARGE"] = 1200] = "EXTRA_LARGE";
15969
+ ScreenWidth[ScreenWidth["LARGE"] = 992] = "LARGE";
15970
+ ScreenWidth[ScreenWidth["MEDIUM"] = 768] = "MEDIUM";
15971
+ ScreenWidth[ScreenWidth["SMALL"] = 576] = "SMALL";
15972
+ ScreenWidth[ScreenWidth["EXTRA_SMALL"] = 575] = "EXTRA_SMALL";
15973
+ })(ScreenWidth || (ScreenWidth = {}));
15974
+ var ScreenOrientation;
15975
+ (function (ScreenOrientation) {
15976
+ ScreenOrientation["PORTRAIT"] = "PORTRAIT";
15977
+ ScreenOrientation["LANDSCAPE"] = "LANDSCAPE";
15978
+ })(ScreenOrientation || (ScreenOrientation = {}));
15979
+ const DATEPICKER_ENABLE_BREAKPOINT = 768;
15980
+ const MOBILE_USERAGENT_REGEX = /Mobi/i;
15981
+ class ScreenStateService {
15982
+ constructor(_document) {
15983
+ this._document = _document;
15984
+ this.isUserAgentMobile = false;
15985
+ this.screenWidthSubject = new ReplaySubject(1);
15986
+ this.screenOrientationSubject = new ReplaySubject(1);
15987
+ window.addEventListener('resize', () => {
15988
+ this.onResize();
15989
+ });
15990
+ window.addEventListener('orientationchange', () => {
15991
+ this.onOrientationChange();
15992
+ });
15993
+ this.determineMobileUserAgent();
15994
+ // Execute them once for initial values
15995
+ this.onResize();
15996
+ this.onOrientationChange();
15997
+ }
15998
+ getScreenWidthChanged() {
15999
+ return this.screenWidthSubject;
16000
+ }
16001
+ getMobileStateChanged() {
16002
+ return this.getScreenWidthChanged().pipe(map$2(screenWidth => screenWidth < DATEPICKER_ENABLE_BREAKPOINT && this.isUserAgentMobile), distinctUntilChanged$1());
16003
+ }
16004
+ getScreenOrientationChanged() {
16005
+ return this.screenOrientationSubject;
16006
+ }
16007
+ onResize() {
16008
+ const windowWidth = window.innerWidth;
16009
+ this.screenWidthSubject.next(windowWidth);
16010
+ }
16011
+ onOrientationChange() {
16012
+ const prevOrientation = this.screenOrientation;
16013
+ const orientation = window.orientation;
16014
+ // Probably not always correct since this depends on the devices default orientation.
16015
+ // Better way to identify this would probably be comparing height & width of the screen.
16016
+ if (orientation === -90 || orientation === 90) {
16017
+ this.screenOrientation = ScreenOrientation.LANDSCAPE;
16018
+ }
16019
+ else {
16020
+ this.screenOrientation = ScreenOrientation.PORTRAIT;
16021
+ }
16022
+ if (this.screenOrientation !== prevOrientation) {
16023
+ this.screenOrientationSubject.next(this.screenOrientation);
16024
+ }
16025
+ }
16026
+ determineMobileUserAgent() {
16027
+ if (this._document) {
16028
+ this.isUserAgentMobile = MOBILE_USERAGENT_REGEX.test(this._document.defaultView.navigator.userAgent);
16029
+ }
16030
+ }
16031
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ScreenStateService, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable }); }
16032
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ScreenStateService, providedIn: 'root' }); }
16033
+ }
16034
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ScreenStateService, decorators: [{
16035
+ type: Injectable,
16036
+ args: [{ providedIn: 'root' }]
16037
+ }], ctorParameters: () => [{ type: Document, decorators: [{
16038
+ type: Inject,
16039
+ args: [DOCUMENT]
16040
+ }] }] });
16041
+
16042
+ class WindowResizeDirective {
16043
+ constructor() {
16044
+ this.debounce = input(250);
16045
+ this.includeFirst = input(false);
16046
+ this.windowResize = output({ alias: 'cngWindowResize' });
16047
+ this.screenStateService = inject(ScreenStateService);
16048
+ this.destroyRef = inject(DestroyRef);
16049
+ }
16050
+ ngOnInit() {
16051
+ const skipCount = this.includeFirst() ? 0 : 1;
16052
+ this.screenStateService
16053
+ .getScreenWidthChanged()
16054
+ .pipe(takeUntilDestroyed(this.destroyRef), skip(skipCount), debounceTime(this.debounce()))
16055
+ .subscribe(width => this.windowResize.emit(width));
16056
+ }
16057
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: WindowResizeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
16058
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.18", type: WindowResizeDirective, isStandalone: true, selector: "[cngWindowResize]", inputs: { debounce: { classPropertyName: "debounce", publicName: "debounce", isSignal: true, isRequired: false, transformFunction: null }, includeFirst: { classPropertyName: "includeFirst", publicName: "includeFirst", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { windowResize: "cngWindowResize" }, ngImport: i0 }); }
16059
+ }
16060
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: WindowResizeDirective, decorators: [{
16061
+ type: Directive,
16062
+ args: [{
16063
+ selector: '[cngWindowResize]',
16064
+ }]
16065
+ }] });
16066
+
16067
+ class BarChartComponent extends ChartBase {
16068
+ static { this.HORIZONTAL_BAR_MIN_HEIGHT_PX = 25; }
16069
+ constructor() {
16070
+ super();
16071
+ this.data = input.required();
16072
+ this.stackLabels = input(undefined);
16073
+ this.orientation = input.required();
16074
+ this.tooltipOrientation = input('top');
16075
+ this.barSizePx = input(15);
16076
+ this.barAreaSizePx = input(40);
16077
+ this.tooltipPercentOfTotal = input('of total');
16078
+ this.tooltipPercentOf = input('of');
16079
+ this.noItemsMessage = input(NO_ITEMS_MESSAGE);
16080
+ this.tooManyItemsMessage = input(TOO_MANY_ITEMS_MESSAGE);
16081
+ this.tooManyItemsGroupedMessage = input(TOO_MANY_ITEMS_GROUPED_MESSAGE);
16082
+ this.showLegend = input(true);
16083
+ this.showExportButton = input(false);
16084
+ this.exportFilename = input('bar-chart');
16085
+ /** Optional label rendered below the X axis. */
16086
+ this.xAxisLabel = input('');
16087
+ /** Optional label rendered rotated to the left of the Y axis. */
16088
+ this.yAxisLabel = input('');
16089
+ this.valueClicked = output();
16090
+ this.textRenderer = new TextRenderer();
16091
+ this.MARGIN = { top: 10, right: 20, bottom: 30, left: 65 };
16092
+ this.toChartColor = toChartColor;
16093
+ this.total = computed(() => this.data().reduce((acc, v) => acc + v.value, 0));
16094
+ this.totalByStack = computed(() => {
16095
+ const totals = {};
16096
+ for (const d of this.slicedDataPoints()) {
16097
+ totals[d.stackKey] = (totals[d.stackKey] ?? 0) + d.value;
16098
+ }
16099
+ return totals;
16100
+ });
16101
+ this.keysByStack = computed(() => {
16102
+ const keys = {};
16103
+ for (const d of this.slicedDataPoints()) {
16104
+ if (!keys[d.stackKey]) {
16105
+ keys[d.stackKey] = [];
16106
+ }
16107
+ keys[d.stackKey].push(d.key);
16108
+ }
16109
+ return keys;
16110
+ });
16111
+ // Count the total amount of bars by either the stacKey (if stacked) or the key (if not stacked)
16112
+ this.totalBarCount = computed(() => new Set(this.data()
16113
+ .filter(d => d.value > 0)
16114
+ .map(d => d.stackKey ?? d.key)).size);
16115
+ this.showingBarCount = computed(() => new Set(this.slicedDataPoints()
16116
+ .filter(d => d.value > 0)
16117
+ .map(d => d.stackKey)).size);
16118
+ this.alertMessageAndType = computed(() => {
16119
+ if (this.loading()) {
16120
+ return undefined;
16121
+ }
16122
+ if (!this.showingBarCount()) {
16123
+ return [this.noItemsMessage(), NO_ITEMS_ALERT_TYPE];
16124
+ }
16125
+ else if (this.totalBarCount() !== this.showingBarCount()) {
16126
+ return [
16127
+ this.stackLabels() ? this.tooManyItemsGroupedMessage() : this.tooManyItemsMessage(),
16128
+ TOO_MANY_ITEMS_ALERT_TYPE,
16129
+ ];
16130
+ }
16131
+ return undefined;
16132
+ });
16133
+ this.legendItems = computed(() => {
16134
+ if (!this.showLegend() || !this.data()?.length) {
16135
+ return [];
16136
+ }
16137
+ if (this.stackLabels()?.length) {
16138
+ // Stacked: one legend entry per distinct label (= each layer in the stack)
16139
+ const seen = new Set();
16140
+ const items = [];
16141
+ for (const item of this.data()) {
16142
+ const label = item.fullLabel ?? item.label;
16143
+ if (!seen.has(label)) {
16144
+ seen.add(label);
16145
+ items.push({ label, color: item.color });
16146
+ }
16147
+ }
16148
+ return items;
16149
+ }
16150
+ // Non-stacked: one entry per bar
16151
+ return this.data().map(item => ({ label: item.fullLabel ?? item.label, color: item.color }));
16152
+ });
16153
+ /** Computed values used by the tooltip to avoid inline logic in the template. */
16154
+ this.tooltipKey = computed(() => {
16155
+ const item = this.selectedItem();
16156
+ if (!item) {
16157
+ return undefined;
16158
+ }
16159
+ return this.stackLabels()?.length ? this.keysByStack()[item.stackKey] ?? [] : [item.key];
16160
+ });
16161
+ this.tooltipLabel = computed(() => {
16162
+ const item = this.selectedItem();
16163
+ if (!item) {
16164
+ return undefined;
16165
+ }
16166
+ return this.stackLabels()?.length ? item.stackKey : item.fullLabel ?? item.label;
16167
+ });
16168
+ this.tooltipValue = computed(() => {
16169
+ const item = this.selectedItem();
16170
+ if (!item) {
16171
+ return undefined;
16172
+ }
16173
+ return this.stackLabels()?.length ? this.totalByStack()[item.stackKey] ?? 0 : item.value;
16174
+ });
16175
+ /** Slices belonging to the currently selected stack – used by the tooltip @for loop. */
16176
+ this.selectedStackSlices = computed(() => {
16177
+ const item = this.selectedItem();
16178
+ if (!item || !this.stackLabels()?.length) {
16179
+ return [];
16180
+ }
16181
+ return this.slicedDataPoints().filter(d => d.stackKey === item.stackKey);
16182
+ });
16183
+ this.maxAmountOfItems = signal(undefined);
16184
+ this.slicedDataPoints = signal([]);
16185
+ this.barSelection = null;
16186
+ this.labelSelection = null;
16187
+ }
16188
+ ngOnChanges(_changes) {
16189
+ if (!this.svg) {
16190
+ return;
16191
+ }
16192
+ requestAnimationFrame(() => this.updateChart());
16193
+ }
16194
+ ngAfterViewInit() {
16195
+ this.createChart();
16196
+ super.ngAfterViewInit(); // schedules the initial requestAnimationFrame(() => updateChart())
16197
+ }
16198
+ createChart() {
16199
+ const element = this.chartRef().nativeElement;
16200
+ this.svg = select(element);
16201
+ }
16202
+ updateChart() {
16203
+ this.svg.selectAll('*').remove();
16204
+ if (this.loading()) {
16205
+ this.slicedDataPoints.set([]);
16206
+ return;
16207
+ }
16208
+ const { width: containerWidth, height: containerHeight } = this.getContainerDimensions();
16209
+ const extraBottom = this.xAxisLabel() ? 16 : 0;
16210
+ const extraLeft = this.yAxisLabel() ? 16 : 0;
16211
+ const leftMargin = (this.orientation() === 'horizontal' ? this.MARGIN.left : 30) + extraLeft;
16212
+ const width = containerWidth - leftMargin - this.MARGIN.right;
16213
+ const height = containerHeight - this.MARGIN.top - (this.MARGIN.bottom + extraBottom);
16214
+ this.maxAmountOfItems.set(this.getMaxAmountOfItems(width, height));
16215
+ if (!this.data()?.length) {
16216
+ this.slicedDataPoints.set([]);
16217
+ return;
16218
+ }
16219
+ const stacks = this.stackItems(this.data());
16220
+ const slicedStacks = stacks.slice(0, this.maxAmountOfItems());
16221
+ const flatStacks = slicedStacks.flat();
16222
+ this.slicedDataPoints.set(flatStacks);
16223
+ const slicedStackKeys = new Set(slicedStacks.map(stack => stack[0].stackKey));
16224
+ const labels = this.stackLabels()?.length
16225
+ ? this.stackLabels()
16226
+ .filter(label => slicedStackKeys.has(label))
16227
+ .map(label => ({ stackKey: label, label }))
16228
+ : flatStacks.map(d => ({ stackKey: d.stackKey, label: d.label }));
16229
+ const g = this.svg
16230
+ .attr('width', width)
16231
+ .attr('height', height)
16232
+ .append('g')
16233
+ .attr('transform', `translate(${leftMargin},${this.MARGIN.top})`);
16234
+ if (this.orientation() === 'vertical') {
16235
+ this.createVerticalChart(g, labels, width, height, this.xAxisLabel(), this.yAxisLabel(), leftMargin);
16236
+ }
16237
+ else {
16238
+ this.createHorizontalChart(g, labels, width, height, this.xAxisLabel(), this.yAxisLabel(), leftMargin);
16239
+ }
16240
+ this.styleGridLines(g);
16241
+ }
16242
+ createVerticalChart(g, labels, width, height, xAxisLabel, yAxisLabel, leftMargin) {
16243
+ const keys = labels.map(d => d.stackKey);
16244
+ const x = scaleBand().domain(keys).range([0, width]);
16245
+ const y = scaleLinear()
16246
+ .domain([0, max(this.slicedDataPoints(), (d) => d.stackValue1) || 0])
16247
+ .nice()
16248
+ .range([height, 0]);
16249
+ // X Axis
16250
+ const labelSelection = g
16251
+ .append('g')
16252
+ .attr('transform', `translate(0,${height})`)
16253
+ .call(axisBottom(x))
16254
+ .selectAll('text');
16255
+ this.labelSelection = labelSelection
16256
+ .data(labels)
16257
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16258
+ .call(this.addTextCommonInfo)
16259
+ .style('cursor', 'pointer')
16260
+ .call(this.addTextAndTitle.bind(this), x.bandwidth())
16261
+ .call(this.addTextHoverHandlers.bind(this))
16262
+ .call(this.addTextClickHandler.bind(this));
16263
+ // Y Axis
16264
+ const tickValues = y.ticks(5).filter((tick) => Number.isInteger(tick));
16265
+ const yAxis = g
16266
+ .append('g')
16267
+ .call(axisLeft(y).tickValues(tickValues).tickSize(-width).tickFormat(format('~s')))
16268
+ .selectAll('text');
16269
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16270
+ yAxis.call(this.addTextCommonInfo);
16271
+ this.createBarSelectionGroups(g);
16272
+ // Add highlight rect (behind)
16273
+ this.addVerticalBarRectangle(x, y, 2)
16274
+ .attr('class', 'bar-highlight')
16275
+ .attr('fill', '#fff')
16276
+ .style('stroke', (d) => toChartColor(d.color))
16277
+ .attr('stroke-width', 2)
16278
+ .style('opacity', 0); // hidden by default;
16279
+ // Add main bar rect (on top)
16280
+ this.addVerticalBarRectangle(x, y)
16281
+ .attr('class', 'bar')
16282
+ .style('fill', (d) => toChartColor(d.color));
16283
+ this.appendAxisLabel(g, xAxisLabel, width / 2, height + 40);
16284
+ this.appendAxisLabel(g, yAxisLabel, -height / 2, -(leftMargin - 8), 'rotate(-90)');
16285
+ }
16286
+ createHorizontalChart(g, labels, width, height, xAxisLabel, yAxisLabel, leftMargin) {
16287
+ const x = scaleLinear()
16288
+ .domain([0, max(this.slicedDataPoints(), (d) => d.stackValue1) || 0])
16289
+ .nice()
16290
+ .range([0, width]);
16291
+ const keys = labels.map(d => d.stackKey);
16292
+ const y = scaleBand().domain(keys).range([0, height]);
16293
+ // X Axis
16294
+ const tickValues = x.ticks(5).filter((tick) => Number.isInteger(tick));
16295
+ const xAxis = g
16296
+ .append('g')
16297
+ .attr('transform', `translate(0,${height})`)
16298
+ .call(axisBottom(x).tickValues(tickValues).tickSize(-height).tickFormat(format('~s')))
16299
+ .selectAll('text');
16300
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16301
+ xAxis.call(this.addTextCommonInfo);
16302
+ // Y Axis
16303
+ const labelSelection = g
16304
+ .append('g')
16305
+ .attr('transform', `translate(0,0)`)
16306
+ .call(axisLeft(y))
16307
+ .selectAll('text');
16308
+ this.labelSelection = labelSelection
16309
+ .data(labels)
16310
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16311
+ .call(this.addTextCommonInfo)
16312
+ .style('cursor', 'pointer')
16313
+ .call(this.addTextAndTitle.bind(this), this.MARGIN.left - 10)
16314
+ .call(this.addTextHoverHandlers.bind(this))
16315
+ .call(this.addTextClickHandler.bind(this));
16316
+ this.createBarSelectionGroups(g);
16317
+ // Add highlight rect (behind)
16318
+ this.addHorizontalBarRectangle(x, y, 2)
16319
+ .attr('class', 'bar-highlight')
16320
+ .attr('fill', '#fff')
16321
+ .style('stroke', (d) => toChartColor(d.color))
16322
+ .attr('stroke-width', 2)
16323
+ .style('opacity', 0); // hidden by default;
16324
+ // Add main bar rect (on top)
16325
+ this.addHorizontalBarRectangle(x, y)
16326
+ .attr('class', 'bar')
16327
+ .style('fill', (d) => toChartColor(d.color));
16328
+ // X axis description label (value axis at bottom)
16329
+ this.appendAxisLabel(g, xAxisLabel, width / 2, height + 40);
16330
+ // Y axis description label: centred in the extra band reserved for it
16331
+ this.appendAxisLabel(g, yAxisLabel, -height / 2, -((this.MARGIN.left + leftMargin) / 2), 'rotate(-90)');
16332
+ }
16333
+ createBarSelectionGroups(g) {
16334
+ this.barSelection = g
16335
+ .selectAll('.bar-group')
16336
+ .data(this.slicedDataPoints(), (d) => d.key)
16337
+ .join('g')
16338
+ .attr('class', 'bar-group')
16339
+ .style('cursor', 'pointer')
16340
+ .on('mouseover', (_e, d) => this.setHoverStylesByStackKey(d.stackKey, d.key, true))
16341
+ .on('mouseout', (_e, d) => this.setHoverStylesByStackKey(d.stackKey, d.key, false))
16342
+ .call(this.addBarClickHandler.bind(this));
16343
+ }
16344
+ addTextCommonInfo(g) {
16345
+ g.style('font-size', '11px').style('fill', 'var(--clr-color-neutral-600, #666)');
16346
+ }
16347
+ addTextHoverHandlers(g) {
16348
+ g.on('mouseover', (_e, d) => this.setHoverStylesByStackKey(d.stackKey, undefined, true)) //
16349
+ .on('mouseout', (_e, d) => this.setHoverStylesByStackKey(d.stackKey, undefined, false));
16350
+ }
16351
+ setHoverStylesByStackKey(stackKey, key, isHover) {
16352
+ // When hovering a stack section, we also need to highlight the label, but not the other sections of the same stack.
16353
+ // But when hovering a label, we need to highlight all sections of the stack.
16354
+ const barGroup = this.barSelection.filter((d) => d.stackKey === stackKey && (key == null || d.key === key));
16355
+ barGroup.select('.bar').style('mix-blend-mode', isHover ? 'multiply' : 'unset');
16356
+ barGroup.select('.bar-highlight').style('opacity', isHover ? '1' : '0');
16357
+ this.labelSelection
16358
+ .filter((d) => d.stackKey === stackKey)
16359
+ .style('font-weight', isHover ? 'bold' : 'unset');
16360
+ }
16361
+ addBarClickHandler(g) {
16362
+ g.on('click', (event, d) => {
16363
+ event.stopPropagation();
16364
+ this.openTooltipByKey(d.key);
16365
+ });
16366
+ }
16367
+ addTextClickHandler(g) {
16368
+ // no click handler for labels in stacked charts
16369
+ if (this.stackLabels()?.length) {
16370
+ return;
16371
+ }
16372
+ g.on('click', (event, d) => {
16373
+ event.stopPropagation();
16374
+ // Note: In non-stacked charts, the stackKey is equal to the key, so this works - but the logic is confusing [VU3REQ-4790]
16375
+ this.openTooltipByKey(d.stackKey);
16376
+ });
16377
+ }
16378
+ openTooltipByKey(key) {
16379
+ const index = this.slicedDataPoints().findIndex(item => item.key === key);
16380
+ const rect = this.barSelection
16381
+ .filter((_d, i) => i === index)
16382
+ // we actually want the bar, not the group (because if value is 0, then the group is not positioned correctly
16383
+ .node()
16384
+ .children[0].getBoundingClientRect();
16385
+ const container = this.chartRef().nativeElement.getBoundingClientRect();
16386
+ this.tooltipPosition.set({
16387
+ x: rect.left - container.left + rect.width / 2,
16388
+ y: (this.tooltipOrientation() === 'top' ? rect.top : rect.bottom) - container.top,
16389
+ });
16390
+ this.selectedItem.set(this.slicedDataPoints()[index]);
16391
+ }
16392
+ addTextAndTitle(g, availableWidth) {
16393
+ g.each((d, index, nodes) => {
16394
+ const target = select(nodes[index]);
16395
+ target.text(this.textRenderer.render(d.label, 15, availableWidth, target.style('font-size'), target.style('font-family')));
16396
+ target.append('title').text(d.label);
16397
+ });
16398
+ }
16399
+ styleGridLines(g) {
16400
+ g.selectAll('.tick line').style('stroke', 'var(--clr-color-neutral-200, #e8e8e8)');
16401
+ }
16402
+ addVerticalBarRectangle(x, y, extraSize = 0) {
16403
+ return this.barSelection
16404
+ .append('rect')
16405
+ .attr('x', (d) => {
16406
+ return (x(d.stackKey) || 0) + (x.bandwidth() - this.barSizePx()) / 2 + extraSize;
16407
+ })
16408
+ .attr('y', (d) => y(d.stackValue1))
16409
+ .attr('width', this.barSizePx() - 2 * extraSize)
16410
+ .attr('height', (d) => y(d.stackValue0) - y(d.stackValue1))
16411
+ .attr('rx', 1)
16412
+ .attr('ry', 1);
16413
+ }
16414
+ addHorizontalBarRectangle(x, y, extraSize = 0) {
16415
+ return this.barSelection
16416
+ .append('rect')
16417
+ .attr('x', (d) => x(d.stackValue0))
16418
+ .attr('y', (d) => {
16419
+ return (y(d.stackKey) || 0) + (y.bandwidth() - this.barSizePx()) / 2 + extraSize;
16420
+ })
16421
+ .attr('width', (d) => x(d.stackValue1) - x(d.stackValue0))
16422
+ .attr('height', this.barSizePx() - 2 * extraSize)
16423
+ .attr('rx', 1)
16424
+ .attr('ry', 1);
16425
+ }
16426
+ /** Appends a shared-style axis description label to the chart group. */
16427
+ appendAxisLabel(g, text, x, y, transform) {
16428
+ if (!text) {
16429
+ return;
16430
+ }
16431
+ const el = g
16432
+ .append('text')
16433
+ .attr('text-anchor', 'middle')
16434
+ .style('font-size', '12px')
16435
+ .style('fill', 'var(--clr-color-neutral-600, #666)')
16436
+ .text(text)
16437
+ .attr('x', x)
16438
+ .attr('y', y);
16439
+ if (transform) {
16440
+ el.attr('transform', transform);
16441
+ }
16442
+ }
16443
+ stackItems(data) {
16444
+ let groups;
16445
+ if (this.stackLabels()?.length) {
16446
+ groups = Array.from(group(data, (d) => d.stackKey ?? d.key));
16447
+ }
16448
+ else {
16449
+ groups = data.map(item => [item.key, [item]]);
16450
+ }
16451
+ return this.mapItems(groups);
16452
+ }
16453
+ mapItems(groups) {
16454
+ const result = [];
16455
+ for (const [_, items] of groups) {
16456
+ let stackValue = 0;
16457
+ const stackSum = sum(items, (d) => d.value);
16458
+ result.push(items.map(item => {
16459
+ const stackValue0 = stackValue;
16460
+ stackValue += item.value;
16461
+ return {
16462
+ ...item,
16463
+ stackValue0,
16464
+ stackValue1: stackValue,
16465
+ stackKey: item.stackKey ?? item.key,
16466
+ percentageOfStack: stackSum === 0 ? 0 : (100 * item.value) / stackSum,
16467
+ };
16468
+ }));
16469
+ }
16470
+ return result;
16471
+ }
16472
+ getMaxAmountOfItems(width, height) {
16473
+ if (this.orientation() === 'vertical') {
16474
+ return Math.floor(width / this.barAreaSizePx());
16475
+ }
16476
+ return Math.floor(height / BarChartComponent.HORIZONTAL_BAR_MIN_HEIGHT_PX);
16477
+ }
16478
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: BarChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
16479
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: BarChartComponent, isStandalone: false, selector: "clr-bar-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, stackLabels: { classPropertyName: "stackLabels", publicName: "stackLabels", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: true, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, barSizePx: { classPropertyName: "barSizePx", publicName: "barSizePx", isSignal: true, isRequired: false, transformFunction: null }, barAreaSizePx: { classPropertyName: "barAreaSizePx", publicName: "barAreaSizePx", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOfTotal: { classPropertyName: "tooltipPercentOfTotal", publicName: "tooltipPercentOfTotal", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOf: { classPropertyName: "tooltipPercentOf", publicName: "tooltipPercentOf", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooManyItemsMessage: { classPropertyName: "tooManyItemsMessage", publicName: "tooManyItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooManyItemsGroupedMessage: { classPropertyName: "tooManyItemsGroupedMessage", publicName: "tooManyItemsGroupedMessage", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, xAxisLabel: { classPropertyName: "xAxisLabel", publicName: "xAxisLabel", isSignal: true, isRequired: false, transformFunction: null }, yAxisLabel: { classPropertyName: "yAxisLabel", publicName: "yAxisLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !data()?.length\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"!stackLabels() ? selectedItem()?.color : undefined\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"valueClicked.emit({ key: tooltipKey(), label: tooltipLabel(), value: tooltipValue() })\"\n >\n <ng-container ngProjectAs=\"cng-title\"> ({{ tooltipValue() }}) {{ tooltipLabel() }}</ng-container>\n\n <p class=\"mt-0\">\n {{ (100 * tooltipValue()) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n\n @if (stackLabels()) { @let stackKey = selectedItem().stackKey; @for (slice of selectedStackSlices(); track\n slice.key) {\n <div class=\"mt-0-5 d-flex\">\n <div class=\"color-square mr-0-5\" [style.background-color]=\"toChartColor(slice.color)\"></div>\n <strong>{{ slice.fullLabel || slice.label }}:&nbsp;</strong>\n <span\n class=\"has-more-info\"\n (click)=\"\n valueClicked.emit({\n key: [slice.key],\n label: slice.fullLabel || slice.label,\n value: slice.value,\n })\n \"\n >\n {{ slice.value }}\n </span>\n </div>\n <p class=\"mt-0-25 percentage-info\">\n {{ (100 * slice.value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n {{ slice.percentageOfStack | number : '1.0-2' }}% {{ tooltipPercentOf() }} \"{{ stackKey }}\"\n </p>\n } }\n </cng-chart-tooltip>\n } @if (loading() || !data()?.length) {\n <cng-bar-chart-skeleton [orientation]=\"orientation()\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && data()?.length) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.percentage-info{margin-left:15px}\n"], dependencies: [{ kind: "component", type: ChartAlertOverlayComponent, selector: "cng-chart-alert-overlay", inputs: ["alertMessage", "alertType"] }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartLegendComponent, selector: "cng-chart-legend", inputs: ["items"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16480
+ }
16481
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: BarChartComponent, decorators: [{
16482
+ type: Component,
16483
+ args: [{ selector: 'clr-bar-chart', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !data()?.length\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"!stackLabels() ? selectedItem()?.color : undefined\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"valueClicked.emit({ key: tooltipKey(), label: tooltipLabel(), value: tooltipValue() })\"\n >\n <ng-container ngProjectAs=\"cng-title\"> ({{ tooltipValue() }}) {{ tooltipLabel() }}</ng-container>\n\n <p class=\"mt-0\">\n {{ (100 * tooltipValue()) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n\n @if (stackLabels()) { @let stackKey = selectedItem().stackKey; @for (slice of selectedStackSlices(); track\n slice.key) {\n <div class=\"mt-0-5 d-flex\">\n <div class=\"color-square mr-0-5\" [style.background-color]=\"toChartColor(slice.color)\"></div>\n <strong>{{ slice.fullLabel || slice.label }}:&nbsp;</strong>\n <span\n class=\"has-more-info\"\n (click)=\"\n valueClicked.emit({\n key: [slice.key],\n label: slice.fullLabel || slice.label,\n value: slice.value,\n })\n \"\n >\n {{ slice.value }}\n </span>\n </div>\n <p class=\"mt-0-25 percentage-info\">\n {{ (100 * slice.value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n {{ slice.percentageOfStack | number : '1.0-2' }}% {{ tooltipPercentOf() }} \"{{ stackKey }}\"\n </p>\n } }\n </cng-chart-tooltip>\n } @if (loading() || !data()?.length) {\n <cng-bar-chart-skeleton [orientation]=\"orientation()\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && data()?.length) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.percentage-info{margin-left:15px}\n"] }]
16484
+ }], ctorParameters: () => [] });
16485
+
16486
+ /*
16487
+ * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
16488
+ * This software is released under MIT license.
16489
+ * The full license information can be found in LICENSE in the root directory of this project.
16490
+ */
16491
+ /**
16492
+ * Renders interactive dot circles for a single XY series onto the given D3 group.
16493
+ *
16494
+ * - Default radius: 4px, hover radius: 6px.
16495
+ * - Calls `onClick` with the clicked element, data point and series when a dot is clicked.
16496
+ *
16497
+ */
16498
+ function renderDots(g, series, x, y, onClick) {
16499
+ g.selectAll(`.dot-${series.key}`)
16500
+ .data(series.data, (d) => d.x)
16501
+ .join('circle')
16502
+ .attr('class', `dot dot-${series.key}`)
16503
+ .attr('cx', (d) => x(d.x) ?? 0)
16504
+ .attr('cy', (d) => y(d.value))
16505
+ .attr('r', 4)
16506
+ .style('fill', toChartColor(series.color))
16507
+ .attr('stroke', '#fff')
16508
+ .attr('stroke-width', 2)
16509
+ .style('cursor', 'pointer')
16510
+ .on('mouseover', (e) => {
16511
+ select(e.currentTarget).attr('r', 6);
16512
+ })
16513
+ .on('mouseout', (e) => {
16514
+ select(e.currentTarget).attr('r', 4);
16515
+ })
16516
+ .on('click', (event, d) => {
16517
+ event.stopPropagation();
16518
+ onClick(event.currentTarget, d, series);
16519
+ });
16520
+ }
16521
+
16522
+ /*
16523
+ * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
16524
+ * This software is released under MIT license.
16525
+ * The full license information can be found in LICENSE in the root directory of this project.
16526
+ */
16527
+ /**
16528
+ * Draws the X and Y axes (with integer-only Y ticks and grid lines) plus
16529
+ * optional description labels onto the given D3 group.
16530
+ *
16531
+ * @param g The D3 group element to append into.
16532
+ * @param x Any D3 axis scale keyed on strings (`ScalePoint` or `ScaleBand`).
16533
+ * @param y The linear Y scale.
16534
+ * @param opts Axis configuration options.
16535
+ */
16536
+ function drawXYAxes(g, x, y, opts) {
16537
+ const { xLabelMap, width, height, xAxisLabel, yAxisLabel, effectiveLeft } = opts;
16538
+ // X axis
16539
+ g.append('g')
16540
+ .attr('transform', `translate(0,${height})`)
16541
+ .call(axisBottom(x).tickFormat((d) => xLabelMap.get(d) ?? d))
16542
+ .selectAll('text')
16543
+ .style('font-size', '11px')
16544
+ .style('fill', 'var(--clr-color-neutral-600, #666)');
16545
+ // Y axis
16546
+ const tickValues = y.ticks(5).filter((t) => Number.isInteger(t));
16547
+ g.append('g')
16548
+ .call(axisLeft(y).tickValues(tickValues).tickSize(-width).tickFormat(format('~s')))
16549
+ .selectAll('text')
16550
+ .style('font-size', '11px')
16551
+ .style('fill', 'var(--clr-color-neutral-600, #666)');
16552
+ // Optional X axis description label
16553
+ if (xAxisLabel) {
16554
+ g.append('text')
16555
+ .attr('x', width / 2)
16556
+ .attr('y', height + 40)
16557
+ .attr('text-anchor', 'middle')
16558
+ .style('font-size', '12px')
16559
+ .style('fill', 'var(--clr-color-neutral-600, #666)')
16560
+ .text(xAxisLabel);
16561
+ }
16562
+ // Optional Y axis description label (rotated, centred in the left-margin band)
16563
+ if (yAxisLabel) {
16564
+ g.append('text')
16565
+ .attr('transform', 'rotate(-90)')
16566
+ .attr('x', -height / 2)
16567
+ .attr('y', -(effectiveLeft / 2))
16568
+ .attr('text-anchor', 'middle')
16569
+ .style('font-size', '12px')
16570
+ .style('fill', 'var(--clr-color-neutral-600, #666)')
16571
+ .text(yAxisLabel);
16572
+ }
16573
+ }
16574
+ /**
16575
+ * Styles all D3 grid tick lines inside `g` with the neutral chart grid color.
16576
+ */
16577
+ function styleGridLines(g) {
16578
+ g.selectAll('.tick line').style('stroke', 'var(--clr-color-neutral-200, #e8e8e8)');
16579
+ }
16580
+
16581
+ class LineChartComponent extends ChartBase {
16582
+ constructor() {
16583
+ super(...arguments);
16584
+ this.series = input.required();
16585
+ this.tooltipOrientation = input('top');
16586
+ this.showArea = input(false);
16587
+ this.showLegend = input(true);
16588
+ this.showValues = input(false);
16589
+ this.showExportButton = input(false);
16590
+ this.exportFilename = input('line-chart');
16591
+ this.noItemsMessage = input(NO_ITEMS_MESSAGE);
16592
+ this.tooltipPercentOfTotal = input('of total');
16593
+ /** Optional label rendered below the X axis. */
16594
+ this.xAxisLabel = input('');
16595
+ /** Optional label rendered rotated to the left of the Y axis. */
16596
+ this.yAxisLabel = input('');
16597
+ this.valueClicked = output();
16598
+ this.MARGIN = { top: 20, right: 30, bottom: 30, left: 65 };
16599
+ this.hasData = computed(() => this.series().some(s => s.data.length > 0));
16600
+ this.total = computed(() => this.series().reduce((acc, s) => acc + s.data.reduce((a, d) => a + d.value, 0), 0));
16601
+ this.alertMessageAndType = computed(() => {
16602
+ if (this.loading()) {
16603
+ return undefined;
16604
+ }
16605
+ if (!this.hasData()) {
16606
+ return [this.noItemsMessage(), NO_ITEMS_ALERT_TYPE];
16607
+ }
16608
+ return undefined;
16609
+ });
16610
+ this.legendItems = computed(() => {
16611
+ if (!this.showLegend()) {
16612
+ return [];
16613
+ }
16614
+ return this.series().map(s => ({ label: s.label, color: s.color }));
16615
+ });
16616
+ }
16617
+ ngOnChanges(_changes) {
16618
+ if (!this.svg) {
16619
+ return;
16620
+ }
16621
+ requestAnimationFrame(() => this.updateChart());
16622
+ }
16623
+ ngAfterViewInit() {
16624
+ this.svg = select(this.chartRef().nativeElement);
16625
+ super.ngAfterViewInit();
16626
+ }
16627
+ updateChart() {
16628
+ this.svg.selectAll('*').remove();
16629
+ if (this.loading()) {
16630
+ return;
16631
+ }
16632
+ if (!this.hasData()) {
16633
+ return;
16634
+ }
16635
+ const { width: containerWidth, height: containerHeight } = this.getContainerDimensions();
16636
+ const extraBottom = this.xAxisLabel() ? 16 : 0;
16637
+ const extraLeft = this.yAxisLabel() ? 16 : 0;
16638
+ const extraTop = this.showValues() ? 14 : 0;
16639
+ const effectiveLeft = this.MARGIN.left + extraLeft;
16640
+ const width = containerWidth - effectiveLeft - this.MARGIN.right;
16641
+ const height = containerHeight - (this.MARGIN.top + extraTop) - (this.MARGIN.bottom + extraBottom);
16642
+ // Collect all unique X keys in the order they appear across all series
16643
+ const xKeys = [...new Set(this.series().flatMap(s => s.data.map(d => d.x)))];
16644
+ // Build a display-label map
16645
+ const xLabelMap = new Map();
16646
+ this.series().forEach(s => s.data.forEach(d => xLabelMap.set(d.x, d.xLabel ?? d.x)));
16647
+ const x = scalePoint().domain(xKeys).range([0, width]).padding(0.5);
16648
+ const maxY = max(this.series().flatMap(s => s.data.map(d => d.value))) ?? 0;
16649
+ const y = scaleLinear().domain([0, maxY]).nice().range([height, 0]);
16650
+ const g = this.svg
16651
+ .attr('width', width)
16652
+ .attr('height', height)
16653
+ .append('g')
16654
+ .attr('transform', `translate(${effectiveLeft},${this.MARGIN.top + extraTop})`);
16655
+ drawXYAxes(g, x, y, {
16656
+ xLabelMap,
16657
+ width,
16658
+ height,
16659
+ xAxisLabel: this.xAxisLabel(),
16660
+ yAxisLabel: this.yAxisLabel(),
16661
+ effectiveLeft,
16662
+ });
16663
+ styleGridLines(g);
16664
+ this.drawSeries(g, x, y);
16665
+ }
16666
+ drawSeries(g, x, y) {
16667
+ const lineGenerator = line()
16668
+ .x((d) => x(d.x) ?? 0)
16669
+ .y((d) => y(d.value))
16670
+ .curve(curveMonotoneX);
16671
+ for (const series of this.series()) {
16672
+ if (!series.data.length) {
16673
+ continue;
16674
+ }
16675
+ // Optional area fill
16676
+ if (this.showArea()) {
16677
+ g.append('path')
16678
+ .datum(series.data)
16679
+ .attr('class', `area area-${series.key}`)
16680
+ .style('fill', toChartColor(series.color))
16681
+ .attr('fill-opacity', 0.1)
16682
+ .attr('d', lineGenerator);
16683
+ }
16684
+ // Line path
16685
+ g.append('path')
16686
+ .datum(series.data)
16687
+ .attr('class', `line line-${series.key}`)
16688
+ .attr('fill', 'none')
16689
+ .style('stroke', toChartColor(series.color))
16690
+ .attr('stroke-width', 2)
16691
+ .attr('stroke-linejoin', 'round')
16692
+ .attr('stroke-linecap', 'round')
16693
+ .attr('d', lineGenerator);
16694
+ // Dots
16695
+ renderDots(g, series, x, y, (el, point, s) => this.openTooltip(el, point, s));
16696
+ // Value labels above each dot
16697
+ if (this.showValues()) {
16698
+ g.selectAll(`.value-label-${series.key}`)
16699
+ .data(series.data, (d) => d.x)
16700
+ .join('text')
16701
+ .attr('class', `value-label value-label-${series.key}`)
16702
+ .attr('x', (d) => x(d.x) ?? 0)
16703
+ .attr('y', (d) => y(d.value) - 9)
16704
+ .attr('text-anchor', 'middle')
16705
+ .style('font-size', '11px')
16706
+ .style('font-weight', '600')
16707
+ .style('fill', toChartColor(series.color))
16708
+ .attr('stroke', '#fff')
16709
+ .attr('stroke-width', 3)
16710
+ .attr('paint-order', 'stroke fill')
16711
+ .style('pointer-events', 'none')
16712
+ .text((d) => format('~s')(d.value));
16713
+ }
16714
+ }
16715
+ }
16716
+ openTooltip(el, point, series) {
16717
+ const rect = el.getBoundingClientRect();
16718
+ const container = this.chartRef().nativeElement.getBoundingClientRect();
16719
+ this.tooltipPosition.set({
16720
+ x: rect.left - container.left + rect.width / 2,
16721
+ y: (this.tooltipOrientation() === 'top' ? rect.top : rect.bottom) - container.top,
16722
+ });
16723
+ this.selectedItem.set({ ...point, seriesKey: series.key, seriesLabel: series.label, color: series.color });
16724
+ }
16725
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: LineChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
16726
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: LineChartComponent, isStandalone: false, selector: "clr-line-chart", inputs: { series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: true, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, showArea: { classPropertyName: "showArea", publicName: "showArea", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showValues: { classPropertyName: "showValues", publicName: "showValues", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOfTotal: { classPropertyName: "tooltipPercentOfTotal", publicName: "tooltipPercentOfTotal", isSignal: true, isRequired: false, transformFunction: null }, xAxisLabel: { classPropertyName: "xAxisLabel", publicName: "xAxisLabel", isSignal: true, isRequired: false, transformFunction: null }, yAxisLabel: { classPropertyName: "yAxisLabel", publicName: "yAxisLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.line,.area{pointer-events:none}.dot{transition:r .1s ease}\n"], dependencies: [{ kind: "component", type: ChartAlertOverlayComponent, selector: "cng-chart-alert-overlay", inputs: ["alertMessage", "alertType"] }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartLegendComponent, selector: "cng-chart-legend", inputs: ["items"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16727
+ }
16728
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: LineChartComponent, decorators: [{
16729
+ type: Component,
16730
+ args: [{ selector: 'clr-line-chart', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.line,.area{pointer-events:none}.dot{transition:r .1s ease}\n"] }]
16731
+ }] });
16732
+
16733
+ class AreaChartComponent extends ChartBase {
16734
+ constructor() {
16735
+ super(...arguments);
16736
+ this.series = input.required();
16737
+ this.tooltipOrientation = input('top');
16738
+ this.showLegend = input(true);
16739
+ this.showExportButton = input(false);
16740
+ this.exportFilename = input('area-chart');
16741
+ /** Area fill opacity (0–1). Default: 0.2. */
16742
+ this.areaOpacity = input(0.2);
16743
+ this.noItemsMessage = input(NO_ITEMS_MESSAGE);
16744
+ this.tooltipPercentOfTotal = input('of total');
16745
+ /** Optional label rendered below the X axis. */
16746
+ this.xAxisLabel = input('');
16747
+ /** Optional label rendered rotated to the left of the Y axis. */
16748
+ this.yAxisLabel = input('');
16749
+ this.valueClicked = output();
16750
+ this.MARGIN = { top: 20, right: 30, bottom: 30, left: 65 };
16751
+ this.hasData = computed(() => this.series().some(s => s.data.length > 0));
16752
+ this.total = computed(() => this.series().reduce((acc, s) => acc + s.data.reduce((a, d) => a + d.value, 0), 0));
16753
+ this.alertMessageAndType = computed(() => {
16754
+ if (this.loading()) {
16755
+ return undefined;
16756
+ }
16757
+ if (!this.hasData()) {
16758
+ return [this.noItemsMessage(), NO_ITEMS_ALERT_TYPE];
16759
+ }
16760
+ return undefined;
16761
+ });
16762
+ this.legendItems = computed(() => {
16763
+ if (!this.showLegend()) {
16764
+ return [];
16765
+ }
16766
+ return this.series().map(s => ({ label: s.label, color: s.color }));
16767
+ });
16768
+ }
16769
+ ngOnChanges(_changes) {
16770
+ if (!this.svg) {
16771
+ return;
16772
+ }
16773
+ requestAnimationFrame(() => this.updateChart());
16774
+ }
16775
+ ngAfterViewInit() {
16776
+ this.svg = select(this.chartRef().nativeElement);
16777
+ super.ngAfterViewInit();
16778
+ }
16779
+ updateChart() {
16780
+ this.svg.selectAll('*').remove();
16781
+ if (this.loading() || !this.hasData()) {
16782
+ return;
16783
+ }
16784
+ const { width: containerWidth, height: containerHeight } = this.getContainerDimensions();
16785
+ const extraBottom = this.xAxisLabel() ? 16 : 0;
16786
+ const extraLeft = this.yAxisLabel() ? 16 : 0;
16787
+ const effectiveLeft = this.MARGIN.left + extraLeft;
16788
+ const width = containerWidth - effectiveLeft - this.MARGIN.right;
16789
+ const height = containerHeight - this.MARGIN.top - (this.MARGIN.bottom + extraBottom);
16790
+ const xKeys = [...new Set(this.series().flatMap(s => s.data.map(d => d.x)))];
16791
+ const xLabelMap = new Map();
16792
+ this.series().forEach(s => s.data.forEach(d => xLabelMap.set(d.x, d.xLabel ?? d.x)));
16793
+ const x = scalePoint().domain(xKeys).range([0, width]).padding(0.5);
16794
+ const maxY = max(this.series().flatMap(s => s.data.map(d => d.value))) ?? 0;
16795
+ const y = scaleLinear().domain([0, maxY]).nice().range([height, 0]);
16796
+ const g = this.svg
16797
+ .attr('width', width)
16798
+ .attr('height', height)
16799
+ .append('g')
16800
+ .attr('transform', `translate(${effectiveLeft},${this.MARGIN.top})`);
16801
+ drawXYAxes(g, x, y, {
16802
+ xLabelMap,
16803
+ width,
16804
+ height,
16805
+ xAxisLabel: this.xAxisLabel(),
16806
+ yAxisLabel: this.yAxisLabel(),
16807
+ effectiveLeft,
16808
+ });
16809
+ styleGridLines(g);
16810
+ this.drawSeries(g, x, y, height);
16811
+ }
16812
+ drawSeries(g, x, y, height) {
16813
+ const areaGenerator = area()
16814
+ .x((d) => x(d.x) ?? 0)
16815
+ .y0(height)
16816
+ .y1((d) => y(d.value))
16817
+ .curve(curveMonotoneX);
16818
+ const lineGenerator = line()
16819
+ .x((d) => x(d.x) ?? 0)
16820
+ .y((d) => y(d.value))
16821
+ .curve(curveMonotoneX);
16822
+ // Draw areas first (behind lines)
16823
+ for (const series of this.series()) {
16824
+ if (!series.data.length) {
16825
+ continue;
16826
+ }
16827
+ g.append('path')
16828
+ .datum(series.data)
16829
+ .attr('class', `area area-${series.key}`)
16830
+ .style('fill', toChartColor(series.color))
16831
+ .attr('fill-opacity', this.areaOpacity())
16832
+ .attr('stroke', 'none')
16833
+ .attr('d', areaGenerator);
16834
+ }
16835
+ // Draw lines and dots on top
16836
+ for (const series of this.series()) {
16837
+ if (!series.data.length) {
16838
+ continue;
16839
+ }
16840
+ g.append('path')
16841
+ .datum(series.data)
16842
+ .attr('class', `line line-${series.key}`)
16843
+ .attr('fill', 'none')
16844
+ .style('stroke', toChartColor(series.color))
16845
+ .attr('stroke-width', 2)
16846
+ .attr('stroke-linejoin', 'round')
16847
+ .attr('stroke-linecap', 'round')
16848
+ .attr('d', lineGenerator);
16849
+ renderDots(g, series, x, y, (el, point, s) => this.openTooltip(el, point, s));
16850
+ }
16851
+ }
16852
+ openTooltip(el, point, series) {
16853
+ const rect = el.getBoundingClientRect();
16854
+ const container = this.chartRef().nativeElement.getBoundingClientRect();
16855
+ this.tooltipPosition.set({
16856
+ x: rect.left - container.left + rect.width / 2,
16857
+ y: (this.tooltipOrientation() === 'top' ? rect.top : rect.bottom) - container.top,
16858
+ });
16859
+ this.selectedItem.set({ ...point, seriesKey: series.key, seriesLabel: series.label, color: series.color });
16860
+ }
16861
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AreaChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
16862
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: AreaChartComponent, isStandalone: false, selector: "clr-area-chart", inputs: { series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: true, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, areaOpacity: { classPropertyName: "areaOpacity", publicName: "areaOpacity", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOfTotal: { classPropertyName: "tooltipPercentOfTotal", publicName: "tooltipPercentOfTotal", isSignal: true, isRequired: false, transformFunction: null }, xAxisLabel: { classPropertyName: "xAxisLabel", publicName: "xAxisLabel", isSignal: true, isRequired: false, transformFunction: null }, yAxisLabel: { classPropertyName: "yAxisLabel", publicName: "yAxisLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.line,.area{pointer-events:none}.dot{transition:r .1s ease}\n"], dependencies: [{ kind: "component", type: ChartAlertOverlayComponent, selector: "cng-chart-alert-overlay", inputs: ["alertMessage", "alertType"] }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartLegendComponent, selector: "cng-chart-legend", inputs: ["items"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16863
+ }
16864
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AreaChartComponent, decorators: [{
16865
+ type: Component,
16866
+ args: [{ selector: 'clr-area-chart', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.line,.area{pointer-events:none}.dot{transition:r .1s ease}\n"] }]
16867
+ }] });
16868
+
16869
+ class ComboChartComponent extends ChartBase {
16870
+ constructor() {
16871
+ super(...arguments);
16872
+ // ── Inputs ──────────────────────────────────────────────────────────────────
16873
+ this.barSeries = input([]);
16874
+ this.lineSeries = input([]);
16875
+ this.tooltipOrientation = input('top');
16876
+ this.showLegend = input(true);
16877
+ this.showExportButton = input(false);
16878
+ this.exportFilename = input('combo-chart');
16879
+ this.noItemsMessage = input(NO_ITEMS_MESSAGE);
16880
+ this.tooltipPercentOfTotal = input('of total');
16881
+ /** Optional label rendered below the X axis. */
16882
+ this.xAxisLabel = input('');
16883
+ /** Optional label rendered rotated to the left of the Y axis (bar scale). */
16884
+ this.yAxisLabel = input('');
16885
+ /** Optional label rendered rotated to the right of the Y axis (line scale). */
16886
+ this.yLineAxisLabel = input('');
16887
+ // ── Outputs ─────────────────────────────────────────────────────────────────
16888
+ this.valueClicked = output();
16889
+ // ── Layout ───────────────────────────────────────────────────────────────────
16890
+ this.MARGIN = { top: 20, right: 30, bottom: 30, left: 65 };
16891
+ // ── Computed ─────────────────────────────────────────────────────────────────
16892
+ this.hasData = computed(() => this.barSeries().some(s => s.data.length > 0) || this.lineSeries().some(s => s.data.length > 0));
16893
+ this.total = computed(() => {
16894
+ const barTotal = this.barSeries().reduce((acc, s) => acc + s.data.reduce((a, d) => a + d.value, 0), 0);
16895
+ const lineTotal = this.lineSeries().reduce((acc, s) => acc + s.data.reduce((a, d) => a + d.value, 0), 0);
16896
+ return barTotal + lineTotal;
16897
+ });
16898
+ this.alertMessageAndType = computed(() => {
16899
+ if (this.loading()) {
16900
+ return undefined;
16901
+ }
16902
+ if (!this.hasData()) {
16903
+ return [this.noItemsMessage(), NO_ITEMS_ALERT_TYPE];
16904
+ }
16905
+ return undefined;
16906
+ });
16907
+ this.legendItems = computed(() => {
16908
+ if (!this.showLegend()) {
16909
+ return [];
16910
+ }
16911
+ const barItems = this.barSeries().map(s => ({ label: s.label, color: s.color }));
16912
+ const lineItems = this.lineSeries().map(s => ({ label: s.label, color: s.color }));
16913
+ return [...barItems, ...lineItems];
16914
+ });
16915
+ }
16916
+ // ── Lifecycle ────────────────────────────────────────────────────────────────
16917
+ ngOnChanges(_changes) {
16918
+ if (!this.svg) {
16919
+ return;
16920
+ }
16921
+ requestAnimationFrame(() => this.updateChart());
16922
+ }
16923
+ ngAfterViewInit() {
16924
+ this.svg = select(this.chartRef().nativeElement);
16925
+ super.ngAfterViewInit();
16926
+ }
16927
+ // ── Chart rendering ──────────────────────────────────────────────────────────
16928
+ updateChart() {
16929
+ this.svg.selectAll('*').remove();
16930
+ if (this.loading() || !this.hasData()) {
16931
+ return;
16932
+ }
16933
+ const { width: cw, height: ch } = this.getContainerDimensions();
16934
+ const extraBottom = this.xAxisLabel() ? 16 : 0;
16935
+ const extraLeft = this.yAxisLabel() ? 16 : 0;
16936
+ const effectiveLeft = this.MARGIN.left + extraLeft;
16937
+ // Reserve extra right margin when line series are present (right Y axis)
16938
+ const hasLines = this.lineSeries().some(s => s.data.length > 0);
16939
+ const extraRight = hasLines ? (this.yLineAxisLabel() ? 50 : 40) : 0;
16940
+ const width = cw - effectiveLeft - this.MARGIN.right - extraRight;
16941
+ const height = ch - this.MARGIN.top - (this.MARGIN.bottom + extraBottom);
16942
+ // Collect all unique X keys preserving order
16943
+ const allBarKeys = this.barSeries().flatMap(s => s.data.map(d => d.x));
16944
+ const allLineKeys = this.lineSeries().flatMap(s => s.data.map(d => d.x));
16945
+ const xKeys = [...new Set([...allBarKeys, ...allLineKeys])];
16946
+ // Build X-label map
16947
+ const xLabelMap = new Map();
16948
+ this.barSeries().forEach(s => s.data.forEach(d => xLabelMap.set(d.x, d.xLabel ?? d.x)));
16949
+ // Outer X scale (one band per category)
16950
+ const x = scaleBand().domain(xKeys).range([0, width]).paddingInner(0.35).paddingOuter(0.15);
16951
+ // ── Separate Y scales ────────────────────────────────────────────────────
16952
+ // Bar Y scale (left axis)
16953
+ const xStackTotals = new Map();
16954
+ for (const series of this.barSeries()) {
16955
+ for (const point of series.data) {
16956
+ xStackTotals.set(point.x, (xStackTotals.get(point.x) ?? 0) + point.value);
16957
+ }
16958
+ }
16959
+ const maxBar = max([...xStackTotals.values()]) ?? 0;
16960
+ const yBar = scaleLinear()
16961
+ .domain([0, maxBar || 1])
16962
+ .nice()
16963
+ .range([height, 0]);
16964
+ // Line Y scale (right axis)
16965
+ const maxLine = max(this.lineSeries().flatMap(s => s.data.map(d => d.value))) ?? 0;
16966
+ const yLine = scaleLinear()
16967
+ .domain([0, maxLine || 1])
16968
+ .nice()
16969
+ .range([height, 0]);
16970
+ const g = this.svg
16971
+ .attr('width', width)
16972
+ .attr('height', height)
16973
+ .append('g')
16974
+ .attr('transform', `translate(${effectiveLeft},${this.MARGIN.top})`);
16975
+ drawXYAxes(g, x, yBar, {
16976
+ xLabelMap,
16977
+ width,
16978
+ height,
16979
+ xAxisLabel: this.xAxisLabel(),
16980
+ yAxisLabel: this.yAxisLabel(),
16981
+ effectiveLeft,
16982
+ });
16983
+ styleGridLines(g);
16984
+ // ── Right Y axis for line series ─────────────────────────────────────────
16985
+ if (hasLines) {
16986
+ const lineTickValues = yLine.ticks(5).filter((t) => Number.isInteger(t));
16987
+ const rightAxisG = g
16988
+ .append('g')
16989
+ .attr('transform', `translate(${width},0)`)
16990
+ .call(axisRight(yLine).tickValues(lineTickValues).tickFormat(format('~s')).tickSize(-width) // draw grid lines across the chart
16991
+ );
16992
+ rightAxisG.selectAll('text').style('font-size', '11px').style('fill', 'var(--clr-color-neutral-600, #666)');
16993
+ // Style the line-axis grid lines with a distinct dashed color
16994
+ rightAxisG
16995
+ .selectAll('.tick line')
16996
+ .style('stroke', 'var(--clr-color-neutral-400, #b3b3b3)')
16997
+ .style('stroke-dasharray', '4 3')
16998
+ .style('stroke-opacity', '0.8');
16999
+ // Remove the default domain line so it doesn't overlap the chart
17000
+ rightAxisG.select('.domain').remove();
17001
+ if (this.yLineAxisLabel()) {
17002
+ g.append('text')
17003
+ .attr('transform', 'rotate(-90)')
17004
+ .attr('x', -height / 2)
17005
+ .attr('y', width + extraRight - 4)
17006
+ .attr('text-anchor', 'middle')
17007
+ .style('font-size', '12px')
17008
+ .style('fill', 'var(--clr-color-neutral-600, #666)')
17009
+ .text(this.yLineAxisLabel());
17010
+ }
17011
+ }
17012
+ this.drawBars(g, x, yBar);
17013
+ this.drawLines(g, x, yLine);
17014
+ }
17015
+ drawBars(g, x, y) {
17016
+ // Running stack base (cumulative sum) per X key across all bar series
17017
+ const stackBase = new Map();
17018
+ for (const series of this.barSeries()) {
17019
+ if (!series.data.length) {
17020
+ continue;
17021
+ }
17022
+ const stacked = series.data.map(d => ({
17023
+ ...d,
17024
+ base: stackBase.get(d.x) ?? 0,
17025
+ }));
17026
+ // Advance running totals for the next series
17027
+ for (const d of series.data) {
17028
+ stackBase.set(d.x, (stackBase.get(d.x) ?? 0) + d.value);
17029
+ }
17030
+ g.selectAll(`.bar-${series.key}`)
17031
+ .data(stacked, (d) => d.x)
17032
+ .join('rect')
17033
+ .attr('class', `bar bar-${series.key}`)
17034
+ .attr('x', (d) => x(d.x) ?? 0)
17035
+ .attr('y', (d) => y(d.base + d.value))
17036
+ .attr('width', x.bandwidth())
17037
+ .attr('height', (d) => y(d.base) - y(d.base + d.value))
17038
+ .attr('rx', 2)
17039
+ .attr('ry', 2)
17040
+ .style('fill', toChartColor(series.color))
17041
+ .style('cursor', 'pointer')
17042
+ .on('mouseover', (e) => {
17043
+ select(e.currentTarget).attr('fill-opacity', 0.75);
17044
+ })
17045
+ .on('mouseout', (e) => {
17046
+ select(e.currentTarget).attr('fill-opacity', 1);
17047
+ })
17048
+ .on('click', (event, d) => {
17049
+ event.stopPropagation();
17050
+ const rect = event.currentTarget.getBoundingClientRect();
17051
+ const container = this.chartRef().nativeElement.getBoundingClientRect();
17052
+ this.tooltipPosition.set({
17053
+ x: rect.left - container.left + rect.width / 2,
17054
+ y: (this.tooltipOrientation() === 'top' ? rect.top : rect.bottom) - container.top,
17055
+ });
17056
+ this.selectedItem.set({
17057
+ seriesKey: series.key,
17058
+ seriesLabel: series.label,
17059
+ seriesType: 'bar',
17060
+ color: series.color,
17061
+ x: d.x,
17062
+ xLabel: d.xLabel,
17063
+ value: d.value,
17064
+ total: this.total(),
17065
+ });
17066
+ });
17067
+ }
17068
+ }
17069
+ drawLines(g, x, y) {
17070
+ // Map X key → band center position for line/dot placement
17071
+ const lineX = (key) => (x(key) ?? 0) + x.bandwidth() / 2;
17072
+ const lineGenerator = line()
17073
+ .x((d) => lineX(d.x))
17074
+ .y((d) => y(d.value))
17075
+ .curve(curveMonotoneX);
17076
+ for (const series of this.lineSeries()) {
17077
+ if (!series.data.length) {
17078
+ continue;
17079
+ }
17080
+ // Line path
17081
+ g.append('path')
17082
+ .datum(series.data)
17083
+ .attr('class', `combo-line combo-line-${series.key}`)
17084
+ .attr('fill', 'none')
17085
+ .style('stroke', toChartColor(series.color))
17086
+ .attr('stroke-width', 2.5)
17087
+ .attr('stroke-linejoin', 'round')
17088
+ .attr('stroke-linecap', 'round')
17089
+ .attr('d', lineGenerator);
17090
+ // Dots
17091
+ g.selectAll(`.combo-dot-${series.key}`)
17092
+ .data(series.data, (d) => d.x)
17093
+ .join('circle')
17094
+ .attr('class', `combo-dot combo-dot-${series.key}`)
17095
+ .attr('cx', (d) => lineX(d.x))
17096
+ .attr('cy', (d) => y(d.value))
17097
+ .attr('r', 4)
17098
+ .style('fill', toChartColor(series.color))
17099
+ .attr('stroke', '#fff')
17100
+ .attr('stroke-width', 2)
17101
+ .style('cursor', 'pointer')
17102
+ .on('mouseover', (e) => {
17103
+ select(e.currentTarget).attr('r', 6);
17104
+ })
17105
+ .on('mouseout', (e) => {
17106
+ select(e.currentTarget).attr('r', 4);
17107
+ })
17108
+ .on('click', (event, d) => {
17109
+ event.stopPropagation();
17110
+ const el = event.currentTarget;
17111
+ const rect = el.getBoundingClientRect();
17112
+ const container = this.chartRef().nativeElement.getBoundingClientRect();
17113
+ this.tooltipPosition.set({
17114
+ x: rect.left - container.left + rect.width / 2,
17115
+ y: (this.tooltipOrientation() === 'top' ? rect.top : rect.bottom) - container.top,
17116
+ });
17117
+ this.selectedItem.set({
17118
+ seriesKey: series.key,
17119
+ seriesLabel: series.label,
17120
+ seriesType: 'line',
17121
+ color: series.color,
17122
+ x: d.x,
17123
+ value: d.value,
17124
+ total: this.total(),
17125
+ });
17126
+ });
17127
+ }
17128
+ }
17129
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ComboChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
17130
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ComboChartComponent, isStandalone: false, selector: "clr-combo-chart", inputs: { barSeries: { classPropertyName: "barSeries", publicName: "barSeries", isSignal: true, isRequired: false, transformFunction: null }, lineSeries: { classPropertyName: "lineSeries", publicName: "lineSeries", isSignal: true, isRequired: false, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOfTotal: { classPropertyName: "tooltipPercentOfTotal", publicName: "tooltipPercentOfTotal", isSignal: true, isRequired: false, transformFunction: null }, xAxisLabel: { classPropertyName: "xAxisLabel", publicName: "xAxisLabel", isSignal: true, isRequired: false, transformFunction: null }, yAxisLabel: { classPropertyName: "yAxisLabel", publicName: "yAxisLabel", isSignal: true, isRequired: false, transformFunction: null }, yLineAxisLabel: { classPropertyName: "yLineAxisLabel", publicName: "yLineAxisLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n seriesType: selectedItem().seriesType,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / selectedItem().total | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.combo-line{pointer-events:none}.combo-dot{transition:r .1s ease}\n"], dependencies: [{ kind: "component", type: ChartAlertOverlayComponent, selector: "cng-chart-alert-overlay", inputs: ["alertMessage", "alertType"] }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartLegendComponent, selector: "cng-chart-legend", inputs: ["items"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
17131
+ }
17132
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ComboChartComponent, decorators: [{
17133
+ type: Component,
17134
+ args: [{ selector: 'clr-combo-chart', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n seriesType: selectedItem().seriesType,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / selectedItem().total | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.combo-line{pointer-events:none}.combo-dot{transition:r .1s ease}\n"] }]
17135
+ }] });
17136
+
17137
+ class PieChartComponent extends ChartBase {
17138
+ constructor() {
17139
+ super(...arguments);
17140
+ this.data = input.required();
17141
+ this.donut = input(true);
17142
+ this.showLegend = input(true);
17143
+ this.showExportButton = input(false);
17144
+ this.exportFilename = input('pie-chart');
17145
+ this.tooltipOrientation = input('top');
17146
+ this.noItemsMessage = input(NO_ITEMS_MESSAGE);
17147
+ this.tooltipPercentOfTotal = input('of total');
17148
+ this.valueClicked = output();
17149
+ this.hasData = computed(() => this.data()?.some(d => d.value > 0));
17150
+ this.total = computed(() => this.data()?.reduce((acc, d) => acc + d.value, 0) ?? 0);
17151
+ this.alertMessageAndType = computed(() => {
17152
+ if (this.loading()) {
17153
+ return undefined;
17154
+ }
17155
+ if (!this.hasData()) {
17156
+ return [this.noItemsMessage(), NO_ITEMS_ALERT_TYPE];
17157
+ }
17158
+ return undefined;
17159
+ });
17160
+ this.legendItems = computed(() => {
17161
+ if (!this.showLegend()) {
17162
+ return [];
17163
+ }
17164
+ return (this.data() ?? []).filter(d => d.value > 0).map(d => ({ label: d.fullLabel ?? d.label, color: d.color }));
17165
+ });
17166
+ }
17167
+ ngOnChanges(_changes) {
17168
+ if (!this.svg) {
17169
+ return;
17170
+ }
17171
+ requestAnimationFrame(() => this.updateChart());
17172
+ }
17173
+ ngAfterViewInit() {
17174
+ this.svg = select(this.chartRef().nativeElement);
17175
+ super.ngAfterViewInit();
17176
+ }
17177
+ updateChart() {
17178
+ this.svg.selectAll('*').remove();
17179
+ if (this.loading() || !this.hasData()) {
17180
+ return;
17181
+ }
17182
+ const { width, height } = this.getContainerDimensions();
17183
+ const radius = Math.min(width, height) / 2;
17184
+ const innerRadius = this.donut() ? radius * 0.55 : 0;
17185
+ const outerRadius = radius * 0.9;
17186
+ this.arcGen = arc().innerRadius(innerRadius).outerRadius(outerRadius);
17187
+ const hoverArc = arc()
17188
+ .innerRadius(innerRadius)
17189
+ .outerRadius(outerRadius * 1.06);
17190
+ const pieGen = pie()
17191
+ .value(d => d.value)
17192
+ .sort(null);
17193
+ const arcs = pieGen(this.data().filter(d => d.value > 0));
17194
+ const g = this.svg
17195
+ .attr('width', width)
17196
+ .attr('height', height)
17197
+ .append('g')
17198
+ .attr('transform', `translate(${width / 2},${height / 2})`);
17199
+ // Slices
17200
+ g.selectAll('.slice')
17201
+ .data(arcs, (d) => d.data.key)
17202
+ .join('path')
17203
+ .attr('class', 'slice')
17204
+ .attr('d', this.arcGen)
17205
+ .style('fill', (d) => toChartColor(d.data.color))
17206
+ .attr('stroke', '#fff')
17207
+ .attr('stroke-width', 2)
17208
+ .style('cursor', 'pointer')
17209
+ .on('mouseover', (e) => {
17210
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17211
+ select(e.currentTarget).attr('d', hoverArc);
17212
+ })
17213
+ .on('mouseout', (e) => {
17214
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17215
+ select(e.currentTarget).attr('d', this.arcGen);
17216
+ })
17217
+ .on('click', (event, d) => {
17218
+ event.stopPropagation();
17219
+ this.openTooltip(d, width, height);
17220
+ });
17221
+ // Slice labels – only show when the slice angle is large enough to fit text
17222
+ const MIN_ANGLE = 0.4; // ~23°
17223
+ const labelRadius = this.donut() ? (innerRadius + outerRadius) / 2 : outerRadius * 0.65;
17224
+ const labelArc = arc().innerRadius(labelRadius).outerRadius(labelRadius);
17225
+ const total = this.total();
17226
+ const labelGroups = g
17227
+ .selectAll('.slice-label')
17228
+ .data(arcs.filter((d) => d.endAngle - d.startAngle >= MIN_ANGLE), (d) => d.data.key)
17229
+ .join('g')
17230
+ .attr('class', 'slice-label')
17231
+ .attr('transform', (d) => {
17232
+ const [x, y] = labelArc.centroid(d);
17233
+ return `translate(${x},${y})`;
17234
+ })
17235
+ .style('pointer-events', 'none');
17236
+ labelGroups
17237
+ .append('text')
17238
+ .attr('class', 'label-value')
17239
+ .attr('text-anchor', 'middle')
17240
+ .attr('dy', '-0.3em')
17241
+ .style('font-size', '11px')
17242
+ .style('font-weight', '600')
17243
+ .style('fill', '#fff')
17244
+ .style('pointer-events', 'none')
17245
+ .text((d) => format('~s')(d.data.value));
17246
+ labelGroups
17247
+ .append('text')
17248
+ .attr('class', 'label-percent')
17249
+ .attr('text-anchor', 'middle')
17250
+ .attr('dy', '0.9em')
17251
+ .style('font-size', '10px')
17252
+ .style('fill', 'rgba(255, 255, 255, 0.9)')
17253
+ .style('pointer-events', 'none')
17254
+ .text((d) => `${((100 * d.data.value) / total).toFixed(1)}%`);
17255
+ // Center label (donut only)
17256
+ if (this.donut()) {
17257
+ g.append('text')
17258
+ .attr('class', 'center-label')
17259
+ .attr('text-anchor', 'middle')
17260
+ .attr('dy', '-0.1em')
17261
+ .style('font-size', '1.4rem')
17262
+ .style('font-weight', '600')
17263
+ .style('fill', 'var(--clr-color-neutral-900, #21333b)')
17264
+ .text(format('~s')(this.total()));
17265
+ g.append('text')
17266
+ .attr('class', 'center-sublabel')
17267
+ .attr('text-anchor', 'middle')
17268
+ .attr('dy', '1.2em')
17269
+ .style('font-size', '0.65rem')
17270
+ .style('fill', 'var(--clr-color-neutral-600, #666)')
17271
+ .style('text-transform', 'uppercase')
17272
+ .style('letter-spacing', '0.05em')
17273
+ .text('Total');
17274
+ }
17275
+ }
17276
+ openTooltip(d, width, height) {
17277
+ const [cx, cy] = this.arcGen.centroid(d);
17278
+ this.tooltipPosition.set({
17279
+ x: width / 2 + cx,
17280
+ y: height / 2 + cy,
17281
+ });
17282
+ this.selectedItem.set(d.data);
17283
+ }
17284
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: PieChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
17285
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: PieChartComponent, isStandalone: false, selector: "clr-pie-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, donut: { classPropertyName: "donut", publicName: "donut", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOfTotal: { classPropertyName: "tooltipPercentOfTotal", publicName: "tooltipPercentOfTotal", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n key: selectedItem().key,\n label: selectedItem().fullLabel || selectedItem().label,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().fullLabel || selectedItem().label }}\n </ng-container>\n\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.slice{transition:d .15s ease}.label-value{font-size:11px;font-weight:600;fill:#fff}.label-percent{font-size:10px;fill:#ffffffe6}.center-label{font-size:1.4rem;font-weight:600;fill:var(--clr-color-neutral-900, #21333b)}.center-sublabel{font-size:.65rem;fill:var(--clr-color-neutral-600, #666);text-transform:uppercase;letter-spacing:.05em}\n"], dependencies: [{ kind: "component", type: ChartAlertOverlayComponent, selector: "cng-chart-alert-overlay", inputs: ["alertMessage", "alertType"] }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartLegendComponent, selector: "cng-chart-legend", inputs: ["items"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
17286
+ }
17287
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: PieChartComponent, decorators: [{
17288
+ type: Component,
17289
+ args: [{ selector: 'clr-pie-chart', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n key: selectedItem().key,\n label: selectedItem().fullLabel || selectedItem().label,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().fullLabel || selectedItem().label }}\n </ng-container>\n\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.slice{transition:d .15s ease}.label-value{font-size:11px;font-weight:600;fill:#fff}.label-percent{font-size:10px;fill:#ffffffe6}.center-label{font-size:1.4rem;font-weight:600;fill:var(--clr-color-neutral-900, #21333b)}.center-sublabel{font-size:.65rem;fill:var(--clr-color-neutral-600, #666);text-transform:uppercase;letter-spacing:.05em}\n"] }]
17290
+ }] });
17291
+
17292
+ /*
17293
+ * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
17294
+ * This software is released under MIT license.
17295
+ * The full license information can be found in LICENSE in the root directory of this project.
17296
+ */
17297
+ // ─── Constants ────────────────────────────────────────────────────────────────
17298
+ const DEFAULT_SECTION_COLORS = ['#009ADB', '#66D1FF', '#D9D9D9', '#A6A6A6'];
17299
+ const DEFAULT_TEXT_COLORS = ['#eee', '#eee', '#555', '#eee'];
17300
+ const DEFAULT_CHART_LABELS = {
17301
+ total: 'Total',
17302
+ all: 'All',
17303
+ };
17304
+ class FunnelChartComponent extends ChartBase {
17305
+ constructor() {
17306
+ super(...arguments);
17307
+ // ── Inputs ──────────────────────────────────────────────────────────────────
17308
+ this.data = input([]);
17309
+ this.showExportButton = input(false);
17310
+ this.exportFilename = input('funnel-chart');
17311
+ /** Rendering mode. 'default' = horizontal bars with sections; 'centered' = centered trapezoid funnel. */
17312
+ this.orientation = input('default');
17313
+ /** Width reserved on each side for labels (px). */
17314
+ this.textSize = input(200);
17315
+ /** Gap between funnel bars and their side-line labels (px). */
17316
+ this.lineTextPadding = input(8);
17317
+ /** Vertical gap between funnel bars – default mode only (px). */
17318
+ this.barGap = input(2);
17319
+ /** Vertical padding inside the side measurement lines (px). */
17320
+ this.sideLineVerticalPadding = input(4);
17321
+ // ── Centered-mode specific inputs ────────────────────────────────────────────
17322
+ /** Minimum length of the horizontal measurement lines on each side (px). Centered mode only. */
17323
+ this.minLineSize = input(30);
17324
+ /** Ratio of bar height to spacer height. Centered mode only. */
17325
+ this.barToSpacerRatio = input(2);
17326
+ /** Vertical padding inside the center divider lines (px). Centered mode only. */
17327
+ this.middleLineVerticalPadding = input(4);
17328
+ /** Fill color of the centered-mode bars. Accepts hex or CSS custom property. Centered mode only. */
17329
+ this.barColor = input(DEFAULT_SECTION_COLORS[0]);
17330
+ /** Fill color of the trapezoid spacers. Accepts hex or CSS custom property. Centered mode only. */
17331
+ this.spacerColor = input('#D9D9D9');
17332
+ /**
17333
+ * Override the chart labels shown in the tooltip (total / all rows).
17334
+ * Partial – any key not provided falls back to the English default.
17335
+ */
17336
+ this.chartLabels = input({});
17337
+ /**
17338
+ * Override section colors by section key.
17339
+ * Accepts hex values ('#009ADB') or CSS custom properties ('--cds-global-color-blue-800').
17340
+ * Per-section `color` in the data takes priority over this map; this map takes priority over
17341
+ * the built-in default palette.
17342
+ */
17343
+ this.sectionColors = input({});
17344
+ // ── Outputs ─────────────────────────────────────────────────────────────────
17345
+ this.valueClicked = output();
17346
+ // ── Computed ─────────────────────────────────────────────────────────────────
17347
+ this.resolvedChartLabels = computed(() => ({
17348
+ ...DEFAULT_CHART_LABELS,
17349
+ ...this.chartLabels(),
17350
+ }));
17351
+ /** CSS-ready color for the centered-mode bar. */
17352
+ this.centeredBarCssColor = computed(() => toChartColor(this.barColor()));
17353
+ /** CSS-ready spacer color. */
17354
+ this.centeredSpacerCssColor = computed(() => toChartColor(this.spacerColor()));
17355
+ this.total = computed(() => this.data()[0]?.value ?? 0);
17356
+ this.hasData = computed(() => this.data().length > 0);
17357
+ // ── State ─────────────────────────────────────────────────────────────────────
17358
+ this.selectedItemLabel = computed(() => this.selectedItem()?.fullLabel ?? this.selectedItem()?.label ?? '');
17359
+ this.selectedSpacerItem = signal(undefined);
17360
+ this.sectionTooltips = signal([]);
17361
+ }
17362
+ // ── Lifecycle ────────────────────────────────────────────────────────────────
17363
+ ngOnChanges(_changes) {
17364
+ if (!this.svg) {
17365
+ return;
17366
+ }
17367
+ requestAnimationFrame(() => this.updateChart());
17368
+ }
17369
+ ngAfterViewInit() {
17370
+ this.svg = select(this.chartRef().nativeElement);
17371
+ super.ngAfterViewInit();
17372
+ }
17373
+ // ── Chart rendering ──────────────────────────────────────────────────────────
17374
+ updateChart() {
17375
+ if (!this.svg) {
17376
+ return;
17377
+ }
17378
+ this.svg.selectAll('*').remove();
17379
+ if (this.loading() || !this.hasData()) {
17380
+ return;
17381
+ }
17382
+ const { width: containerWidth, height: containerHeight } = this.getContainerDimensions();
17383
+ if (this.orientation() === 'centered') {
17384
+ this.renderCenteredChart(containerWidth, containerHeight);
17385
+ }
17386
+ else {
17387
+ const funnelWidth = this.getFunnelWidth(containerWidth);
17388
+ const barHeight = this.getBarHeight(containerHeight);
17389
+ const dataPoints = this.calculateDataPoints(funnelWidth, barHeight);
17390
+ this.renderRightSideText(dataPoints, containerWidth, barHeight);
17391
+ this.renderLeftSideText(dataPoints, barHeight);
17392
+ this.renderSideLines(dataPoints, containerWidth, funnelWidth, barHeight);
17393
+ this.renderFunnel(dataPoints, containerWidth, containerHeight, funnelWidth, barHeight);
17394
+ }
17395
+ }
17396
+ // ── Data calculation ─────────────────────────────────────────────────────────
17397
+ calculateDataPoints(funnelWidth, barHeight) {
17398
+ const maxValue = max(this.data(), d => d.value) ?? 1;
17399
+ const widthScale = scaleLinear().domain([0, maxValue]).range([0, funnelWidth]);
17400
+ const sectionColorOverrides = this.sectionColors();
17401
+ return this.data().map((d, i) => {
17402
+ let xOffset = 0;
17403
+ const sections = (d.sections ?? []).map((s, si) => {
17404
+ const rawColor = s.color ?? sectionColorOverrides[s.key] ?? DEFAULT_SECTION_COLORS[si % DEFAULT_SECTION_COLORS.length];
17405
+ const cssColor = toChartColor(rawColor);
17406
+ const hoverColor = rawColor.startsWith('--')
17407
+ ? cssColor
17408
+ : color(rawColor)?.darker(0.5)?.formatHex() ?? cssColor;
17409
+ const textColor = s.textColor ?? DEFAULT_TEXT_COLORS[si % DEFAULT_TEXT_COLORS.length];
17410
+ const sWidth = widthScale(s.value);
17411
+ const sec = {
17412
+ key: s.key,
17413
+ label: s.label,
17414
+ x: xOffset,
17415
+ width: sWidth,
17416
+ value: s.value,
17417
+ percentage: d.value ? this.round((s.value / d.value) * 100) : 0,
17418
+ cssColor,
17419
+ hoverColor,
17420
+ textColor,
17421
+ };
17422
+ xOffset += sWidth;
17423
+ return sec;
17424
+ });
17425
+ const point = {
17426
+ ...d,
17427
+ sections,
17428
+ y: i * (barHeight + this.barGap()),
17429
+ width: widthScale(d.value),
17430
+ percentage: this.total() ? this.round((d.value / this.total()) * 100) : 0,
17431
+ };
17432
+ if (i > 0) {
17433
+ const previousValue = this.data()[i - 1].value;
17434
+ point.delta = previousValue - d.value;
17435
+ point.deltaPercentage = previousValue === 0 ? 0 : this.round((point.delta / previousValue) * 100);
17436
+ }
17437
+ return point;
17438
+ });
17439
+ }
17440
+ // ── Rendering helpers ────────────────────────────────────────────────────────
17441
+ renderRightSideText(dataPoints, containerWidth, barHeight) {
17442
+ const rightG = this.svg.append('g').attr('transform', `translate(${containerWidth - this.textSize()},0)`);
17443
+ dataPoints.forEach((d, i) => {
17444
+ if (i === 0) {
17445
+ return;
17446
+ }
17447
+ const prev = dataPoints[i - 1];
17448
+ const labelY = (prev.y + barHeight + d.y) / 2 + 4;
17449
+ rightG
17450
+ .append('text')
17451
+ .attr('x', 0)
17452
+ .attr('y', labelY)
17453
+ .attr('text-anchor', 'start')
17454
+ .style('font-size', '12px')
17455
+ .style('fill', 'var(--clr-color-neutral-600, #666)')
17456
+ .text(`-${this.round(d.deltaPercentage)}% (${d.delta})`);
17457
+ });
17458
+ }
17459
+ renderLeftSideText(dataPoints, barHeight) {
17460
+ const leftG = this.svg.append('g');
17461
+ const ICON_SPACE = 20;
17462
+ const positions = [];
17463
+ dataPoints.forEach(d => {
17464
+ const labelY = (d.y + barHeight + d.y) / 2 + 4;
17465
+ const iconSpacing = d.description ? ICON_SPACE : 0;
17466
+ const textLength = leftG
17467
+ .append('text')
17468
+ .attr('x', this.textSize() - iconSpacing)
17469
+ .attr('y', labelY - 8)
17470
+ .attr('text-anchor', 'end')
17471
+ .style('font-size', '13px')
17472
+ .style('font-weight', '600')
17473
+ .style('fill', 'var(--clr-color-neutral-900, #21333b)')
17474
+ .style('cursor', 'pointer')
17475
+ .text(d.label)
17476
+ .on('click', () => this.valueClicked.emit({ value: d.value, label: d.label, key: d.key }))
17477
+ .node()
17478
+ .getComputedTextLength();
17479
+ if (d.description) {
17480
+ positions.push({
17481
+ x: this.textSize() - iconSpacing + 5,
17482
+ y: labelY - 24,
17483
+ key: d.key,
17484
+ description: d.description,
17485
+ });
17486
+ }
17487
+ leftG
17488
+ .append('line')
17489
+ .attr('x1', this.textSize() - textLength - iconSpacing)
17490
+ .attr('y1', labelY - 5)
17491
+ .attr('x2', this.textSize() - iconSpacing)
17492
+ .attr('y2', labelY - 5)
17493
+ .attr('stroke', 'var(--clr-color-neutral-600, #666)')
17494
+ .attr('stroke-width', 2)
17495
+ .attr('stroke-dasharray', '2,2');
17496
+ leftG
17497
+ .append('text')
17498
+ .attr('x', this.textSize())
17499
+ .attr('y', labelY + 10)
17500
+ .attr('text-anchor', 'end')
17501
+ .style('font-size', '12px')
17502
+ .style('fill', 'var(--clr-color-neutral-600, #666)')
17503
+ .text(`${this.round(d.percentage)}% (${d.value})`);
17504
+ });
17505
+ this.sectionTooltips.set(positions);
17506
+ }
17507
+ renderSideLines(dataPoints, containerWidth, funnelWidth, barHeight) {
17508
+ const lineG = this.svg.append('g').attr('transform', `translate(${(containerWidth - funnelWidth) / 2},0)`);
17509
+ dataPoints.forEach(d => {
17510
+ const midY = d.y + barHeight / 2;
17511
+ lineG
17512
+ .append('line')
17513
+ .attr('x1', 0)
17514
+ .attr('y1', midY)
17515
+ .attr('x2', funnelWidth)
17516
+ .attr('y2', midY)
17517
+ .attr('stroke', '#D9D9D9')
17518
+ .attr('stroke-width', 1);
17519
+ this.renderSideLine(lineG, funnelWidth, d.y, barHeight);
17520
+ if (d.value === 0) {
17521
+ this.renderSideLine(lineG, 0, d.y, barHeight);
17522
+ }
17523
+ });
17524
+ }
17525
+ renderSideLine(g, x, y, barHeight) {
17526
+ g.append('line')
17527
+ .attr('x1', x)
17528
+ .attr('y1', y + this.sideLineVerticalPadding())
17529
+ .attr('x2', x)
17530
+ .attr('y2', y + barHeight - this.sideLineVerticalPadding())
17531
+ .attr('stroke', '#D9D9D9')
17532
+ .attr('stroke-width', 1);
17533
+ }
17534
+ renderFunnel(dataPoints, containerWidth, containerHeight, funnelWidth, barHeight) {
17535
+ const funnelG = this.svg
17536
+ .attr('width', containerWidth)
17537
+ .attr('height', containerHeight)
17538
+ .append('g')
17539
+ .attr('transform', `translate(${(containerWidth - funnelWidth) / 2},0)`);
17540
+ // First pass: all rects (z-order: rects below texts)
17541
+ dataPoints.forEach(dp => {
17542
+ dp.sections.forEach(section => {
17543
+ funnelG
17544
+ .append('rect')
17545
+ .attr('x', section.x)
17546
+ .attr('y', dp.y)
17547
+ .attr('width', section.width)
17548
+ .attr('height', barHeight)
17549
+ .style('fill', section.cssColor)
17550
+ .style('cursor', 'pointer')
17551
+ .on('mouseover', e => {
17552
+ select(e.currentTarget).style('fill', section.hoverColor);
17553
+ })
17554
+ .on('mouseout', e => {
17555
+ select(e.currentTarget).style('fill', section.cssColor);
17556
+ })
17557
+ .on('click', (event) => {
17558
+ this.handleSectionClick(event, dp, section.key);
17559
+ });
17560
+ });
17561
+ });
17562
+ // Second pass: texts on top
17563
+ dataPoints.forEach(dp => {
17564
+ dp.sections.forEach(section => {
17565
+ if (section.width > 40) {
17566
+ funnelG
17567
+ .append('text')
17568
+ .attr('x', section.x + section.width / 2)
17569
+ .attr('y', dp.y + barHeight / 2)
17570
+ .attr('text-anchor', 'middle')
17571
+ .attr('dominant-baseline', 'middle')
17572
+ .style('font-size', '13px')
17573
+ .style('font-weight', '600')
17574
+ .style('pointer-events', 'none')
17575
+ .style('fill', section.textColor)
17576
+ .text(`${section.percentage}%`);
17577
+ }
17578
+ });
17579
+ });
17580
+ }
17581
+ // ── Event handlers ────────────────────────────────────────────────────────────
17582
+ handleSectionClick(event, d, _sectionKey) {
17583
+ event.stopPropagation();
17584
+ const target = event.currentTarget;
17585
+ const rect = target.getBoundingClientRect();
17586
+ const container = this.chartRef().nativeElement.getBoundingClientRect();
17587
+ this.tooltipPosition.set({
17588
+ x: rect.left - container.left + rect.width / 2,
17589
+ y: rect.bottom - container.top,
17590
+ });
17591
+ this.selectedItem.set(d);
17592
+ }
17593
+ resetTooltip() {
17594
+ if (this.selectedItem() || this.selectedSpacerItem() || this.tooltipPosition()) {
17595
+ this.selectedItem.set(undefined);
17596
+ this.selectedSpacerItem.set(undefined);
17597
+ this.tooltipPosition.set(undefined);
17598
+ }
17599
+ }
17600
+ /** Formats a percentage to 2 decimal places for template use. */
17601
+ pct(value, total) {
17602
+ return this.round(total ? (value / total) * 100 : 0);
17603
+ }
17604
+ // ── Centered chart ────────────────────────────────────────────────────────────
17605
+ renderCenteredChart(containerWidth, containerHeight) {
17606
+ const maxFunnelWidth = this.getMaxFunnelWidth(containerWidth);
17607
+ const barHeight = this.getCenteredBarHeight(containerHeight);
17608
+ const spacerHeight = this.getCenteredSpacerHeight(containerHeight);
17609
+ const dataPoints = this.calculateCenteredDataPoints(maxFunnelWidth, barHeight, spacerHeight);
17610
+ const spacerPoints = this.calculateSpacerDataPoints(dataPoints, maxFunnelWidth, barHeight);
17611
+ this.renderCenteredLeftSideText(dataPoints, barHeight);
17612
+ this.renderCenteredLeftSideLines(dataPoints, maxFunnelWidth, barHeight);
17613
+ this.renderCenteredRightSideText(dataPoints, containerWidth, barHeight);
17614
+ this.renderCenteredRightSideLines(dataPoints, containerWidth, maxFunnelWidth, barHeight);
17615
+ this.renderCenteredMiddleLines(dataPoints, spacerPoints, containerWidth, barHeight);
17616
+ this.renderCenteredFunnel(dataPoints, spacerPoints, containerWidth, containerHeight, maxFunnelWidth, barHeight, spacerHeight);
17617
+ }
17618
+ calculateCenteredDataPoints(maxFunnelWidth, barHeight, spacerHeight) {
17619
+ const total = this.data()[0]?.value ?? 1;
17620
+ const maxValue = max(this.data(), d => d.value) ?? 1;
17621
+ const scale = scaleLinear().domain([0, maxValue]).range([0, maxFunnelWidth]);
17622
+ return this.data().map((d, i) => {
17623
+ const point = {
17624
+ ...d,
17625
+ y: i * (barHeight + spacerHeight),
17626
+ width: scale(d.value),
17627
+ percentage: total ? this.round((d.value / total) * 100) : 0,
17628
+ };
17629
+ if (i > 0) {
17630
+ const prev = this.data()[i - 1].value;
17631
+ point.delta = prev - d.value;
17632
+ point.deltaPercentage = prev === 0 ? 0 : this.round((point.delta / prev) * 100);
17633
+ }
17634
+ return point;
17635
+ });
17636
+ }
17637
+ calculateSpacerDataPoints(dataPoints, maxFunnelWidth, barHeight) {
17638
+ const spacers = [];
17639
+ for (let i = 0; i < dataPoints.length - 1; i++) {
17640
+ const cur = dataPoints[i];
17641
+ const next = dataPoints[i + 1];
17642
+ spacers.push({
17643
+ index: i,
17644
+ topWidth: cur.width,
17645
+ bottomWidth: next.width,
17646
+ topY: cur.y + barHeight,
17647
+ bottomY: next.y,
17648
+ topX: (maxFunnelWidth - cur.width) / 2,
17649
+ bottomX: (maxFunnelWidth - next.width) / 2,
17650
+ deltaPercentage: next.deltaPercentage ?? 0,
17651
+ });
17652
+ }
17653
+ return spacers;
17654
+ }
17655
+ renderCenteredLeftSideText(dataPoints, barHeight) {
17656
+ const g = this.svg.append('g').attr('transform', `translate(${this.textSize()},0)`);
17657
+ const ICON_SPACE = 20;
17658
+ const positions = [];
17659
+ dataPoints.forEach(d => {
17660
+ const cy = d.y + barHeight / 2;
17661
+ const iconSpacing = d.description ? ICON_SPACE : 0;
17662
+ const textLen = g
17663
+ .append('text')
17664
+ .attr('x', -iconSpacing)
17665
+ .attr('y', cy - 8)
17666
+ .attr('text-anchor', 'end')
17667
+ .style('font-size', '13px')
17668
+ .style('font-weight', '600')
17669
+ .style('fill', 'var(--clr-color-neutral-900, #21333b)')
17670
+ .style('cursor', 'pointer')
17671
+ .text(d.label)
17672
+ .on('click', () => this.valueClicked.emit({ value: d.value, label: d.label, key: d.key }))
17673
+ .node()
17674
+ .getComputedTextLength();
17675
+ if (d.description) {
17676
+ positions.push({ key: d.key, x: this.textSize() - iconSpacing + 5, y: cy - 24, description: d.description });
17677
+ }
17678
+ g.append('line')
17679
+ .attr('x1', -textLen - iconSpacing)
17680
+ .attr('y1', cy - 5)
17681
+ .attr('x2', -iconSpacing)
17682
+ .attr('y2', cy - 5)
17683
+ .attr('stroke', 'var(--clr-color-neutral-600, #666)')
17684
+ .attr('stroke-width', 2)
17685
+ .attr('stroke-dasharray', '2,2');
17686
+ g.append('text')
17687
+ .attr('x', 0)
17688
+ .attr('y', cy + 10)
17689
+ .attr('text-anchor', 'end')
17690
+ .style('font-size', '12px')
17691
+ .style('fill', 'var(--clr-color-neutral-600, #666)')
17692
+ .text(`${this.round(d.percentage)}% (${d.value})`);
17693
+ });
17694
+ this.sectionTooltips.set(positions);
17695
+ }
17696
+ renderCenteredLeftSideLines(dataPoints, maxFunnelWidth, barHeight) {
17697
+ const g = this.svg.append('g').attr('transform', `translate(${this.textSize() + this.lineTextPadding()},0)`);
17698
+ dataPoints.forEach(d => {
17699
+ g.append('line')
17700
+ .attr('x1', 0)
17701
+ .attr('y1', d.y + barHeight / 2)
17702
+ .attr('x2', maxFunnelWidth / 2 + this.minLineSize())
17703
+ .attr('y2', d.y + barHeight / 2)
17704
+ .attr('stroke', '#D9D9D9')
17705
+ .attr('stroke-width', 1);
17706
+ });
17707
+ }
17708
+ renderCenteredRightSideText(dataPoints, containerWidth, barHeight) {
17709
+ const g = this.svg.append('g').attr('transform', `translate(${containerWidth - this.textSize()},0)`);
17710
+ dataPoints.forEach((d, i) => {
17711
+ if (i === 0) {
17712
+ return;
17713
+ }
17714
+ const prev = dataPoints[i - 1];
17715
+ const cy = (prev.y + barHeight + d.y) / 2 + 4;
17716
+ g.append('text')
17717
+ .attr('x', 0)
17718
+ .attr('y', cy)
17719
+ .attr('text-anchor', 'start')
17720
+ .style('font-size', '12px')
17721
+ .style('fill', 'var(--clr-color-neutral-600, #666)')
17722
+ .text(`-${this.round(d.deltaPercentage)}% (${d.delta})`);
17723
+ });
17724
+ }
17725
+ renderCenteredRightSideLines(dataPoints, containerWidth, maxFunnelWidth, barHeight) {
17726
+ const g = this.svg.append('g').attr('transform', `translate(${containerWidth / 2},0)`);
17727
+ dataPoints.forEach((d, i) => {
17728
+ if (i === 0) {
17729
+ return;
17730
+ }
17731
+ const prev = dataPoints[i - 1];
17732
+ const cy = (prev.y + barHeight + d.y) / 2;
17733
+ g.append('line')
17734
+ .attr('x1', 0)
17735
+ .attr('y1', cy)
17736
+ .attr('x2', maxFunnelWidth / 2 + this.minLineSize())
17737
+ .attr('y2', cy)
17738
+ .attr('stroke', '#D9D9D9')
17739
+ .attr('stroke-width', 1);
17740
+ });
17741
+ }
17742
+ renderCenteredMiddleLines(dataPoints, spacerPoints, containerWidth, barHeight) {
17743
+ const vPad = this.middleLineVerticalPadding();
17744
+ const g = this.svg.append('g').attr('transform', `translate(${containerWidth / 2},0)`);
17745
+ g.selectAll('.bar-mid')
17746
+ .data(dataPoints)
17747
+ .enter()
17748
+ .append('line')
17749
+ .attr('x1', 0)
17750
+ .attr('y1', d => d.y + vPad)
17751
+ .attr('x2', 0)
17752
+ .attr('y2', d => d.y + barHeight - vPad)
17753
+ .attr('stroke', '#D9D9D9')
17754
+ .attr('stroke-width', 1);
17755
+ g.selectAll('.spacer-mid')
17756
+ .data(spacerPoints)
17757
+ .enter()
17758
+ .append('line')
17759
+ .attr('x1', 0)
17760
+ .attr('y1', d => d.topY + vPad)
17761
+ .attr('x2', 0)
17762
+ .attr('y2', d => d.bottomY - vPad)
17763
+ .attr('stroke', '#D9D9D9')
17764
+ .attr('stroke-width', 1);
17765
+ }
17766
+ renderCenteredFunnel(dataPoints, spacerPoints, containerWidth, containerHeight, maxFunnelWidth, barHeight, spacerHeight) {
17767
+ const g = this.svg
17768
+ .attr('width', containerWidth)
17769
+ .attr('height', containerHeight)
17770
+ .append('g')
17771
+ .attr('transform', `translate(${(containerWidth - maxFunnelWidth) / 2},0)`);
17772
+ // ── Bars ──────────────────────────────────────────────────────────────────
17773
+ const rawBarColor = this.barColor();
17774
+ const cssBarColor = toChartColor(rawBarColor);
17775
+ const hoverBarColor = rawBarColor.startsWith('--')
17776
+ ? cssBarColor
17777
+ : color(rawBarColor)?.darker(0.5)?.formatHex() ?? cssBarColor;
17778
+ const bars = g
17779
+ .selectAll('.c-bar')
17780
+ .data(dataPoints)
17781
+ .enter()
17782
+ .append('g')
17783
+ .attr('class', 'c-bar');
17784
+ bars
17785
+ .append('rect')
17786
+ .attr('x', d => (maxFunnelWidth - d.width) / 2)
17787
+ .attr('y', d => d.y)
17788
+ .attr('width', d => d.width)
17789
+ .attr('height', barHeight)
17790
+ .style('fill', cssBarColor)
17791
+ .style('cursor', 'pointer')
17792
+ .on('mouseover', e => {
17793
+ select(e.currentTarget).style('fill', hoverBarColor);
17794
+ })
17795
+ .on('mouseout', e => {
17796
+ select(e.currentTarget).style('fill', cssBarColor);
17797
+ })
17798
+ .on('click', (event, d) => {
17799
+ event.stopPropagation();
17800
+ const rect = event.currentTarget.getBoundingClientRect();
17801
+ const cont = this.chartRef().nativeElement.getBoundingClientRect();
17802
+ this.tooltipPosition.set({ x: rect.left - cont.left + rect.width / 2, y: rect.top - cont.top });
17803
+ this.selectedSpacerItem.set(undefined);
17804
+ this.selectedItem.set(d);
17805
+ });
17806
+ bars
17807
+ .selectAll('.c-bar-label')
17808
+ .data(dataPoints.filter(d => d.width > 40))
17809
+ .enter()
17810
+ .append('text')
17811
+ .attr('x', maxFunnelWidth / 2)
17812
+ .attr('y', d => d.y + barHeight / 2)
17813
+ .attr('text-anchor', 'middle')
17814
+ .attr('dominant-baseline', 'middle')
17815
+ .style('font-size', '13px')
17816
+ .style('font-weight', '600')
17817
+ .style('pointer-events', 'none')
17818
+ .style('fill', '#eee')
17819
+ .text(d => `${this.round(d.percentage)}%`);
17820
+ // ── Spacers ───────────────────────────────────────────────────────────────
17821
+ const rawSpacerColor = this.spacerColor();
17822
+ const cssSpacerColor = toChartColor(rawSpacerColor);
17823
+ const hoverSpacerColor = rawSpacerColor.startsWith('--')
17824
+ ? cssSpacerColor
17825
+ : color(rawSpacerColor)?.darker(0.2)?.formatHex() ?? cssSpacerColor;
17826
+ g.selectAll('.c-spacer')
17827
+ .data(spacerPoints)
17828
+ .enter()
17829
+ .append('polygon')
17830
+ .attr('class', 'c-spacer')
17831
+ .attr('points', d => `${d.topX},${d.topY} ${d.topX + d.topWidth},${d.topY} ${d.bottomX + d.bottomWidth},${d.bottomY} ${d.bottomX},${d.bottomY}`)
17832
+ .style('fill', cssSpacerColor)
17833
+ .style('cursor', 'pointer')
17834
+ .on('mouseover', e => {
17835
+ select(e.currentTarget).style('fill', hoverSpacerColor);
17836
+ })
17837
+ .on('mouseout', e => {
17838
+ select(e.currentTarget).style('fill', cssSpacerColor);
17839
+ })
17840
+ .on('click', (event, d) => {
17841
+ event.stopPropagation();
17842
+ const rect = event.currentTarget.getBoundingClientRect();
17843
+ const cont = this.chartRef().nativeElement.getBoundingClientRect();
17844
+ this.tooltipPosition.set({ x: rect.left - cont.left + rect.width / 2, y: rect.top - cont.top });
17845
+ this.selectedItem.set(undefined);
17846
+ this.selectedSpacerItem.set(d);
17847
+ });
17848
+ g.selectAll('.c-spacer-label')
17849
+ .data(spacerPoints.filter(d => d.bottomWidth > 40))
17850
+ .enter()
17851
+ .append('text')
17852
+ .attr('x', maxFunnelWidth / 2)
17853
+ .attr('y', d => d.topY + spacerHeight / 2)
17854
+ .attr('text-anchor', 'middle')
17855
+ .attr('dominant-baseline', 'middle')
17856
+ .style('font-size', '13px')
17857
+ .style('font-weight', '600')
17858
+ .style('pointer-events', 'none')
17859
+ .style('fill', '#333')
17860
+ .text(d => `-${this.round(d.deltaPercentage)}%`);
17861
+ }
17862
+ // ── Helpers ───────────────────────────────────────────────────────────────────
17863
+ getMaxFunnelWidth(containerWidth) {
17864
+ return containerWidth - this.textSize() * 2 - this.minLineSize() * 2 - this.lineTextPadding() * 2;
17865
+ }
17866
+ getCenteredFractionHeight(containerHeight) {
17867
+ return containerHeight / (this.data().length * (this.barToSpacerRatio() + 1) - 1);
17868
+ }
17869
+ getCenteredBarHeight(containerHeight) {
17870
+ return this.getCenteredFractionHeight(containerHeight) * this.barToSpacerRatio();
17871
+ }
17872
+ getCenteredSpacerHeight(containerHeight) {
17873
+ return this.getCenteredFractionHeight(containerHeight);
17874
+ }
17875
+ getFunnelWidth(containerWidth) {
17876
+ return containerWidth - this.textSize() * 2 - this.lineTextPadding() * 2;
17877
+ }
17878
+ getBarHeight(containerHeight) {
17879
+ const n = this.data().length;
17880
+ if (n === 0) {
17881
+ return 0;
17882
+ }
17883
+ return (containerHeight - (n - 1) * this.barGap()) / n;
17884
+ }
17885
+ round(value, decimals = 2) {
17886
+ const factor = 10 ** decimals;
17887
+ return Math.round(factor * value) / factor;
17888
+ }
17889
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FunnelChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
17890
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: FunnelChartComponent, isStandalone: false, selector: "clr-funnel-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, textSize: { classPropertyName: "textSize", publicName: "textSize", isSignal: true, isRequired: false, transformFunction: null }, lineTextPadding: { classPropertyName: "lineTextPadding", publicName: "lineTextPadding", isSignal: true, isRequired: false, transformFunction: null }, barGap: { classPropertyName: "barGap", publicName: "barGap", isSignal: true, isRequired: false, transformFunction: null }, sideLineVerticalPadding: { classPropertyName: "sideLineVerticalPadding", publicName: "sideLineVerticalPadding", isSignal: true, isRequired: false, transformFunction: null }, minLineSize: { classPropertyName: "minLineSize", publicName: "minLineSize", isSignal: true, isRequired: false, transformFunction: null }, barToSpacerRatio: { classPropertyName: "barToSpacerRatio", publicName: "barToSpacerRatio", isSignal: true, isRequired: false, transformFunction: null }, middleLineVerticalPadding: { classPropertyName: "middleLineVerticalPadding", publicName: "middleLineVerticalPadding", isSignal: true, isRequired: false, transformFunction: null }, barColor: { classPropertyName: "barColor", publicName: "barColor", isSignal: true, isRequired: false, transformFunction: null }, spacerColor: { classPropertyName: "spacerColor", publicName: "spacerColor", isSignal: true, isRequired: false, transformFunction: null }, chartLabels: { classPropertyName: "chartLabels", publicName: "chartLabels", isSignal: true, isRequired: false, transformFunction: null }, sectionColors: { classPropertyName: "sectionColors", publicName: "sectionColors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"funnel-container\" #container (cngWindowResize)=\"updateChart()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (!loading()) { @for (pos of sectionTooltips(); track pos.key) {\n <clr-signpost [style.left.px]=\"pos.x\" [style.top.px]=\"pos.y\">\n <cds-icon clrSignpostTrigger shape=\"info-circle\" size=\"18\" />\n <clr-signpost-content clrPosition=\"bottom-left\">\n <p class=\"mt-0\">{{ pos.description }}</p>\n </clr-signpost-content>\n </clr-signpost>\n } } @if (loading()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" skeletonType=\"loading\" />\n } @if (tooltipPosition()) { @if (orientation() === 'centered') {\n\n <!-- \u2500\u2500 Centered mode tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n tooltipOrientation=\"bottom\"\n [squareColor]=\"selectedSpacerItem() ? centeredSpacerCssColor() : centeredBarCssColor()\"\n [tooltipClickable]=\"!selectedSpacerItem()\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n selectedItem() && valueClicked.emit({\n value: selectedItem().value,\n label: selectedItem().label,\n key: selectedItem().key,\n section: undefined,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n @if (selectedSpacerItem()) {\n {{ data()[selectedSpacerItem().index].label }} \u2192 {{ data()[selectedSpacerItem().index + 1].label }} } @else { ({{\n selectedItem().value\n }}) {{ selectedItem().label }}\n }\n </ng-container>\n\n @if (selectedSpacerItem()) {\n <p class=\"mt-0\">\n <strong\n >{{ data()[selectedSpacerItem().index].value }} \u2192 {{ data()[selectedSpacerItem().index + 1].value }}</strong\n >\n </p>\n @for (section of (data()[selectedSpacerItem().index].sections ?? []); track section.key) { @let drillable =\n section.value > 0;\n <p class=\"tooltip-row mt-0 mt-0-5 d-flex\">\n <strong>{{ section.label }}:&nbsp;</strong>\n <span\n class=\"row-right\"\n [class.has-more-info]=\"drillable\"\n (click)=\"\n drillable && valueClicked.emit({\n value: section.value,\n label: data()[selectedSpacerItem().index].label + ': ' + section.label,\n key: data()[selectedSpacerItem().index].key,\n section: section.key,\n })\n \"\n >{{ section.value }}</span\n >\n &nbsp;({{ pct(section.value, data()[selectedSpacerItem().index].value) }}%)\n </p>\n } } @else {\n <p class=\"mt-0\">{{ pct(selectedItem().value, total()) }}% {{ resolvedChartLabels().all }}</p>\n }\n </cng-chart-tooltip>\n\n } @else {\n\n <!-- \u2500\u2500 Default mode tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n tooltipOrientation=\"bottom\"\n [squareColor]=\"undefined\"\n [tooltipClickable]=\"false\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n >\n <ng-container ngProjectAs=\"cng-title\">{{ selectedItemLabel() }}</ng-container>\n @for (section of selectedItem().sections; track section.key) { @let drillable = section.value > 0;\n <p class=\"tooltip-row mt-0 mt-0-5 d-flex\">\n <span>\n <div class=\"color-square mr-0-5\" [style.background-color]=\"section.cssColor\"></div>\n </span>\n <strong>{{ section.label }}:&nbsp;</strong>\n <span\n class=\"row-right\"\n [class.has-more-info]=\"drillable\"\n (click)=\"\n drillable && valueClicked.emit({\n value: section.value,\n label: selectedItemLabel() + ': ' + section.label,\n key: selectedItem().key,\n section: section.key,\n })\n \"\n >{{ section.value }}</span\n >\n &nbsp;({{ section.percentage }}%)\n </p>\n }\n\n <p class=\"tooltip-row mt-0-5\">\n <strong>{{ resolvedChartLabels().total }}:&nbsp;</strong>\n <span\n class=\"has-more-info\"\n (click)=\"\n valueClicked.emit({\n value: selectedItem().value,\n label: selectedItemLabel(),\n key: selectedItem().key,\n section: undefined\n })\n \"\n >{{ selectedItem().value }}</span\n >\n </p>\n\n <ng-container ngProjectAs=\"cng-footer\">\n <p class=\"tooltip-row mt-0\">\n <strong>{{ resolvedChartLabels().all }}:&nbsp;</strong>\n <span class=\"row-right\">{{ total() }}</span>\n </p>\n </ng-container>\n </cng-chart-tooltip>\n\n } } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"funnel-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.funnel-container{height:100%;width:100%;max-width:1400px;margin:auto;position:relative}svg{width:100%;height:100%}clr-signpost{position:absolute!important}.tooltip-row{display:flex;justify-content:space-between;align-items:center}.row-right{margin-left:auto}.funnel-export-btn{position:absolute;bottom:0;right:0;opacity:0;pointer-events:none;transition:opacity .2s ease}.funnel-container:hover .funnel-export-btn{opacity:1;pointer-events:auto}\n"], dependencies: [{ kind: "directive", type: i1$1.CdsIconCustomTag, selector: "cds-icon" }, { kind: "component", type: i1$1.ClrSignpost, selector: "clr-signpost", inputs: ["clrSignpostTriggerAriaLabel"] }, { kind: "component", type: i1$1.ClrSignpostContent, selector: "clr-signpost-content", inputs: ["clrSignpostCloseAriaLabel", "clrPosition"] }, { kind: "directive", type: i1$1.ClrSignpostTrigger, selector: "[clrSignpostTrigger]" }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
17891
+ }
17892
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FunnelChartComponent, decorators: [{
17893
+ type: Component,
17894
+ args: [{ selector: 'clr-funnel-chart', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div class=\"funnel-container\" #container (cngWindowResize)=\"updateChart()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (!loading()) { @for (pos of sectionTooltips(); track pos.key) {\n <clr-signpost [style.left.px]=\"pos.x\" [style.top.px]=\"pos.y\">\n <cds-icon clrSignpostTrigger shape=\"info-circle\" size=\"18\" />\n <clr-signpost-content clrPosition=\"bottom-left\">\n <p class=\"mt-0\">{{ pos.description }}</p>\n </clr-signpost-content>\n </clr-signpost>\n } } @if (loading()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" skeletonType=\"loading\" />\n } @if (tooltipPosition()) { @if (orientation() === 'centered') {\n\n <!-- \u2500\u2500 Centered mode tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n tooltipOrientation=\"bottom\"\n [squareColor]=\"selectedSpacerItem() ? centeredSpacerCssColor() : centeredBarCssColor()\"\n [tooltipClickable]=\"!selectedSpacerItem()\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n selectedItem() && valueClicked.emit({\n value: selectedItem().value,\n label: selectedItem().label,\n key: selectedItem().key,\n section: undefined,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n @if (selectedSpacerItem()) {\n {{ data()[selectedSpacerItem().index].label }} \u2192 {{ data()[selectedSpacerItem().index + 1].label }} } @else { ({{\n selectedItem().value\n }}) {{ selectedItem().label }}\n }\n </ng-container>\n\n @if (selectedSpacerItem()) {\n <p class=\"mt-0\">\n <strong\n >{{ data()[selectedSpacerItem().index].value }} \u2192 {{ data()[selectedSpacerItem().index + 1].value }}</strong\n >\n </p>\n @for (section of (data()[selectedSpacerItem().index].sections ?? []); track section.key) { @let drillable =\n section.value > 0;\n <p class=\"tooltip-row mt-0 mt-0-5 d-flex\">\n <strong>{{ section.label }}:&nbsp;</strong>\n <span\n class=\"row-right\"\n [class.has-more-info]=\"drillable\"\n (click)=\"\n drillable && valueClicked.emit({\n value: section.value,\n label: data()[selectedSpacerItem().index].label + ': ' + section.label,\n key: data()[selectedSpacerItem().index].key,\n section: section.key,\n })\n \"\n >{{ section.value }}</span\n >\n &nbsp;({{ pct(section.value, data()[selectedSpacerItem().index].value) }}%)\n </p>\n } } @else {\n <p class=\"mt-0\">{{ pct(selectedItem().value, total()) }}% {{ resolvedChartLabels().all }}</p>\n }\n </cng-chart-tooltip>\n\n } @else {\n\n <!-- \u2500\u2500 Default mode tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n tooltipOrientation=\"bottom\"\n [squareColor]=\"undefined\"\n [tooltipClickable]=\"false\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n >\n <ng-container ngProjectAs=\"cng-title\">{{ selectedItemLabel() }}</ng-container>\n @for (section of selectedItem().sections; track section.key) { @let drillable = section.value > 0;\n <p class=\"tooltip-row mt-0 mt-0-5 d-flex\">\n <span>\n <div class=\"color-square mr-0-5\" [style.background-color]=\"section.cssColor\"></div>\n </span>\n <strong>{{ section.label }}:&nbsp;</strong>\n <span\n class=\"row-right\"\n [class.has-more-info]=\"drillable\"\n (click)=\"\n drillable && valueClicked.emit({\n value: section.value,\n label: selectedItemLabel() + ': ' + section.label,\n key: selectedItem().key,\n section: section.key,\n })\n \"\n >{{ section.value }}</span\n >\n &nbsp;({{ section.percentage }}%)\n </p>\n }\n\n <p class=\"tooltip-row mt-0-5\">\n <strong>{{ resolvedChartLabels().total }}:&nbsp;</strong>\n <span\n class=\"has-more-info\"\n (click)=\"\n valueClicked.emit({\n value: selectedItem().value,\n label: selectedItemLabel(),\n key: selectedItem().key,\n section: undefined\n })\n \"\n >{{ selectedItem().value }}</span\n >\n </p>\n\n <ng-container ngProjectAs=\"cng-footer\">\n <p class=\"tooltip-row mt-0\">\n <strong>{{ resolvedChartLabels().all }}:&nbsp;</strong>\n <span class=\"row-right\">{{ total() }}</span>\n </p>\n </ng-container>\n </cng-chart-tooltip>\n\n } } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"funnel-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.funnel-container{height:100%;width:100%;max-width:1400px;margin:auto;position:relative}svg{width:100%;height:100%}clr-signpost{position:absolute!important}.tooltip-row{display:flex;justify-content:space-between;align-items:center}.row-right{margin-left:auto}.funnel-export-btn{position:absolute;bottom:0;right:0;opacity:0;pointer-events:none;transition:opacity .2s ease}.funnel-container:hover .funnel-export-btn{opacity:1;pointer-events:auto}\n"] }]
17895
+ }] });
17896
+
17897
+ const PREFERRED_ORDER = ['top-right', 'top-left', 'bottom-right', 'bottom-left'];
17898
+ class AutoPositionDirective {
17899
+ constructor() {
17900
+ this.signpostContent = inject(ClrSignpostContent, { optional: true });
17901
+ this.elementRef = inject(ElementRef);
17902
+ this.handleMouseDown = () => {
17903
+ // Only calculate when signpost is closed (about to open)
17904
+ if (this.elementRef.nativeElement.classList.contains('is-off-screen')) {
17905
+ this.updatePosition();
17906
+ }
17907
+ };
17908
+ }
17909
+ ngOnInit() {
17910
+ const signpost = this.elementRef.nativeElement.closest('clr-signpost');
17911
+ this.trigger = signpost?.querySelector('[clrSignpostTrigger]');
17912
+ if (this.trigger) {
17913
+ this.trigger.addEventListener('mousedown', this.handleMouseDown);
17914
+ }
17915
+ this.resizeObserver = new ResizeObserver(() => {
17916
+ if (!this.elementRef.nativeElement.classList.contains('is-off-screen')) {
17917
+ this.updatePosition();
17918
+ }
17919
+ });
17920
+ this.resizeObserver.observe(document.body);
17921
+ }
17922
+ updatePosition() {
17923
+ if (!this.signpostContent || !this.trigger) {
17924
+ return;
17925
+ }
17926
+ const triggerRect = this.trigger.getBoundingClientRect();
17927
+ this.signpostContent.position = this.getBestPosition(triggerRect);
17928
+ }
17929
+ getBestPosition(triggerRect) {
17930
+ const contentWidth = 300;
17931
+ const contentHeight = 200;
17932
+ const buffer = 50;
17933
+ const contentHeaderHeight = document.querySelector('.content-header')?.getBoundingClientRect().height ?? 0;
17934
+ const appHeaderHight = document.querySelector('.header')?.getBoundingClientRect().height ?? 0;
17935
+ const headerHeight = contentHeaderHeight + appHeaderHight;
17936
+ // Find first position with enough space
17937
+ for (const pos of PREFERRED_ORDER) {
17938
+ if (this.hasSpace(triggerRect, pos, contentWidth, contentHeight, headerHeight, buffer)) {
17939
+ return pos;
17940
+ }
17941
+ }
17942
+ return PREFERRED_ORDER[0]; // Fallback to first preference
17943
+ }
17944
+ hasSpace(rect, position, width, height, headerHeight, buffer) {
17945
+ const [vertical, horizontal] = position.split('-');
17946
+ const verticalSpace = vertical === 'top'
17947
+ ? rect.top - headerHeight - height - buffer
17948
+ : window.innerHeight - rect.bottom - height - buffer;
17949
+ const horizontalSpace = horizontal === 'left' ? rect.left - width - buffer : window.innerWidth - rect.right - width - buffer;
17950
+ return Math.min(verticalSpace, horizontalSpace) > 0;
17951
+ }
17952
+ ngOnDestroy() {
17953
+ if (this.trigger) {
17954
+ this.trigger.removeEventListener('mousedown', this.handleMouseDown);
17955
+ }
17956
+ this.resizeObserver?.disconnect();
17957
+ }
17958
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AutoPositionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
17959
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.18", type: AutoPositionDirective, isStandalone: true, selector: "clr-signpost-content[autoPosition]", ngImport: i0 }); }
17960
+ }
17961
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AutoPositionDirective, decorators: [{
17962
+ type: Directive,
17963
+ args: [{
17964
+ selector: 'clr-signpost-content[autoPosition]',
17965
+ standalone: true,
17966
+ }]
17967
+ }] });
17968
+
17969
+ class TenantFullDateRangeDirective {
17970
+ constructor() {
17971
+ this.start = contentChild(ClrStartDateInput);
17972
+ this.end = contentChild(ClrEndDateInput);
17973
+ }
17974
+ ngAfterContentInit() {
17975
+ if (this.start()) {
17976
+ const originalStartEmit = this.start().dateChange.emit.bind(this.start().dateChange);
17977
+ this.start().dateChange.emit = date => {
17978
+ originalStartEmit(this.toTenantDateTime(date, '00:00'));
17979
+ };
17980
+ }
17981
+ if (this.end()) {
17982
+ const originalEndEmit = this.end().dateChange.emit.bind(this.end().dateChange);
17983
+ this.end().dateChange.emit = date => {
17984
+ originalEndEmit(this.toTenantDateTime(date, '23:59'));
17985
+ };
17986
+ }
17987
+ }
17988
+ toTenantDateTime(date, time) {
17989
+ if (!date) {
17990
+ return date;
17991
+ }
17992
+ const [hours, minutes] = time.split(':').map(Number);
17993
+ const result = new Date(date);
17994
+ result.setHours(hours, minutes, 0, 0);
17995
+ return result;
17996
+ }
17997
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: TenantFullDateRangeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
17998
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "19.2.18", type: TenantFullDateRangeDirective, isStandalone: true, selector: "clr-date-range-container[cngTenantFullDateRange]", queries: [{ propertyName: "start", first: true, predicate: ClrStartDateInput, descendants: true, isSignal: true }, { propertyName: "end", first: true, predicate: ClrEndDateInput, descendants: true, isSignal: true }], ngImport: i0 }); }
17999
+ }
18000
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: TenantFullDateRangeDirective, decorators: [{
18001
+ type: Directive,
18002
+ args: [{
18003
+ selector: 'clr-date-range-container[cngTenantFullDateRange]',
18004
+ standalone: true,
18005
+ }]
18006
+ }] });
18007
+
18008
+ /*
18009
+ * Copyright (c) 2026 Porsche Informatik. All Rights Reserved.
18010
+ * This software is released under MIT license.
18011
+ * The full license information can be found in LICENSE in the root directory of this project.
18012
+ */
18013
+ const CLR_CHARTS_DECLARATIONS = [
18014
+ AreaChartComponent,
18015
+ BarChartComponent,
18016
+ ComboChartComponent,
18017
+ FunnelChartComponent,
18018
+ LineChartComponent,
18019
+ PieChartComponent,
18020
+ ];
18021
+ class ClrChartsModule {
18022
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrChartsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
18023
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.18", ngImport: i0, type: ClrChartsModule, declarations: [AreaChartComponent,
18024
+ BarChartComponent,
18025
+ ComboChartComponent,
18026
+ FunnelChartComponent,
18027
+ LineChartComponent,
18028
+ PieChartComponent], imports: [CommonModule,
18029
+ DecimalPipe,
18030
+ ClrAlertModule,
18031
+ ClrIconModule,
18032
+ ClrSignpostModule,
18033
+ // standalone helpers consumed by the non-standalone chart components
18034
+ ChartAlertOverlayComponent,
18035
+ ChartExportButtonComponent,
18036
+ ChartLegendComponent,
18037
+ ChartSkeletonComponent,
18038
+ ChartTooltipComponent,
18039
+ OutsideClickDirective,
18040
+ WindowResizeDirective], exports: [AreaChartComponent,
18041
+ BarChartComponent,
18042
+ ComboChartComponent,
18043
+ FunnelChartComponent,
18044
+ LineChartComponent,
18045
+ PieChartComponent] }); }
18046
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrChartsModule, imports: [CommonModule,
18047
+ ClrAlertModule,
18048
+ ClrIconModule,
18049
+ ClrSignpostModule,
18050
+ // standalone helpers consumed by the non-standalone chart components
18051
+ ChartAlertOverlayComponent,
18052
+ ChartExportButtonComponent,
18053
+ ChartSkeletonComponent,
18054
+ ChartTooltipComponent] }); }
18055
+ }
18056
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrChartsModule, decorators: [{
18057
+ type: NgModule,
18058
+ args: [{
18059
+ imports: [
18060
+ CommonModule,
18061
+ DecimalPipe,
18062
+ ClrAlertModule,
18063
+ ClrIconModule,
18064
+ ClrSignpostModule,
18065
+ // standalone helpers consumed by the non-standalone chart components
18066
+ ChartAlertOverlayComponent,
18067
+ ChartExportButtonComponent,
18068
+ ChartLegendComponent,
18069
+ ChartSkeletonComponent,
18070
+ ChartTooltipComponent,
18071
+ OutsideClickDirective,
18072
+ WindowResizeDirective,
18073
+ ],
18074
+ declarations: [...CLR_CHARTS_DECLARATIONS],
18075
+ exports: [...CLR_CHARTS_DECLARATIONS],
18076
+ }]
18077
+ }] });
18078
+
18079
+ /*
18080
+ * Copyright (c) 2026 Porsche Informatik. All Rights Reserved.
18081
+ * This software is released under MIT license.
18082
+ * The full license information can be found in LICENSE in the root directory of this project.
18083
+ */
18084
+
18085
+ /*
18086
+ * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
18087
+ * This software is released under MIT license.
18088
+ * The full license information can be found in LICENSE in the root directory of this project.
18089
+ */
18090
+ class ClrAddonsModule {
18091
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrAddonsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
18092
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.18", ngImport: i0, type: ClrAddonsModule, imports: [ClrFocusFirstInvalidFieldDirective, ClrControlEnterSubmitDirective], exports: [ClrViewEditSectionModule,
18093
+ ClrPagerModule,
18094
+ ClrDotPagerModule,
18095
+ ClrPagedSearchResultListModule,
18096
+ ClrCollapseExpandSectionModule,
18097
+ ClrBreadcrumbModule,
18098
+ ClrMainNavGroupModule,
18099
+ ClrContentPanelModule,
18100
+ ClrNotificationModule,
18101
+ ClrFlowBarModule,
18102
+ ClrBackButtonModule,
18103
+ ClrNumericFieldModule,
18104
+ ClrSearchFieldModule,
18105
+ ClrTreetableModule,
18106
+ ClrProgressSpinnerModule,
18107
+ ClrDateTimeModule,
18108
+ ClrQuickListModule,
18109
+ ClrIconAvatarModule,
18110
+ ClrLetterAvatarModule,
18111
+ ClrMultilingualModule,
18112
+ ClrGenericQuickListModule,
18113
+ ClrDataListValidatorModule,
18114
+ ClrHistoryModule,
18115
+ ClrAutocompleteOffModule,
18116
+ ClrBrandAvatarModule,
18117
+ ClrLocationBarModule,
18118
+ ClrFormModule,
18119
+ ClrDropdownOverflowModule,
18120
+ ClrDatagridStatePersistenceModule,
18121
+ ClrEnumFilterModule,
18122
+ ClrDateFilterModule,
18123
+ ClrDaterangepickerModule,
18124
+ ClrIfWarningModule,
18125
+ ClrActionPanelModule,
18126
+ ClrReadonlyDirectiveModule,
18127
+ ClrDatagridColumnReorderModule,
18128
+ ClrSignpostAddonModule,
18129
+ ClrFocusFirstInvalidFieldDirective,
18130
+ ClrControlEnterSubmitDirective,
18131
+ ClrChartsModule] }); }
18132
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrAddonsModule, imports: [ClrViewEditSectionModule,
18133
+ ClrPagerModule,
18134
+ ClrDotPagerModule,
18135
+ ClrPagedSearchResultListModule,
18136
+ ClrCollapseExpandSectionModule,
18137
+ ClrBreadcrumbModule,
18138
+ ClrMainNavGroupModule,
18139
+ ClrContentPanelModule,
18140
+ ClrNotificationModule,
18141
+ ClrFlowBarModule,
18142
+ ClrBackButtonModule,
18143
+ ClrNumericFieldModule,
18144
+ ClrSearchFieldModule,
18145
+ ClrTreetableModule,
18146
+ ClrProgressSpinnerModule,
18147
+ ClrDateTimeModule,
18148
+ ClrQuickListModule,
18149
+ ClrIconAvatarModule,
18150
+ ClrLetterAvatarModule,
18151
+ ClrMultilingualModule,
18152
+ ClrGenericQuickListModule,
18153
+ ClrDataListValidatorModule,
18154
+ ClrHistoryModule,
18155
+ ClrAutocompleteOffModule,
18156
+ ClrBrandAvatarModule,
18157
+ ClrLocationBarModule,
18158
+ ClrFormModule,
18159
+ ClrDropdownOverflowModule,
18160
+ ClrDatagridStatePersistenceModule,
18161
+ ClrEnumFilterModule,
18162
+ ClrDateFilterModule,
18163
+ ClrDaterangepickerModule,
18164
+ ClrIfWarningModule,
18165
+ ClrActionPanelModule,
18166
+ ClrReadonlyDirectiveModule,
18167
+ ClrDatagridColumnReorderModule,
18168
+ ClrSignpostAddonModule,
18169
+ ClrChartsModule] }); }
18170
+ }
18171
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrAddonsModule, decorators: [{
18172
+ type: NgModule,
18173
+ args: [{
18174
+ imports: [ClrFocusFirstInvalidFieldDirective, ClrControlEnterSubmitDirective],
18175
+ declarations: [],
18176
+ exports: [
18177
+ ClrViewEditSectionModule,
18178
+ ClrPagerModule,
18179
+ ClrDotPagerModule,
18180
+ ClrPagedSearchResultListModule,
18181
+ ClrCollapseExpandSectionModule,
18182
+ ClrBreadcrumbModule,
18183
+ ClrMainNavGroupModule,
18184
+ ClrContentPanelModule,
18185
+ ClrNotificationModule,
18186
+ ClrFlowBarModule,
18187
+ ClrBackButtonModule,
18188
+ ClrNumericFieldModule,
18189
+ ClrSearchFieldModule,
18190
+ ClrTreetableModule,
18191
+ ClrProgressSpinnerModule,
18192
+ ClrDateTimeModule,
18193
+ ClrQuickListModule,
18194
+ ClrIconAvatarModule,
18195
+ ClrLetterAvatarModule,
18196
+ ClrMultilingualModule,
18197
+ ClrGenericQuickListModule,
18198
+ ClrDataListValidatorModule,
18199
+ ClrHistoryModule,
18200
+ ClrAutocompleteOffModule,
18201
+ ClrBrandAvatarModule,
18202
+ ClrLocationBarModule,
18203
+ ClrFormModule,
18204
+ ClrDropdownOverflowModule,
18205
+ ClrDatagridStatePersistenceModule,
18206
+ ClrEnumFilterModule,
18207
+ ClrDateFilterModule,
18208
+ ClrDaterangepickerModule,
18209
+ ClrIfWarningModule,
18210
+ ClrActionPanelModule,
18211
+ ClrReadonlyDirectiveModule,
18212
+ ClrDatagridColumnReorderModule,
18213
+ ClrSignpostAddonModule,
18214
+ ClrFocusFirstInvalidFieldDirective,
18215
+ ClrControlEnterSubmitDirective,
18216
+ ClrChartsModule,
18217
+ ],
18218
+ }]
18219
+ }] });
18220
+
18221
+ /*
18222
+ * Copyright (c) 2018-2024 Porsche Informatik. All Rights Reserved.
18223
+ * This software is released under MIT license.
18224
+ * The full license information can be found in LICENSE in the root directory of this project.
18225
+ */
18226
+
18227
+ var ExportTypeEnum;
18228
+ (function (ExportTypeEnum) {
18229
+ ExportTypeEnum["ALL"] = "ALL";
18230
+ ExportTypeEnum["FILTERED"] = "FILTERED";
18231
+ ExportTypeEnum["SELECTED"] = "SELECTED";
18232
+ })(ExportTypeEnum || (ExportTypeEnum = {}));
18233
+
18234
+ class ExportDatagridService {
18235
+ constructor() {
18236
+ this.init();
18237
+ }
18238
+ async init() {
18239
+ try {
18240
+ this.zipcelx = await import('zipcelx');
18241
+ }
18242
+ catch (error) {
18243
+ console.warn('Optional feature requires `zipcelx`. Please install it: npm install zipcelx', error);
18244
+ }
18245
+ }
18246
+ exportToExcel(filename, headers, rows) {
18247
+ const config = {
18248
+ filename,
18249
+ sheet: {
18250
+ data: [headers, ...rows],
18251
+ },
18252
+ };
18253
+ this.zipcelx.default(config);
18254
+ }
18255
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ExportDatagridService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
18256
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ExportDatagridService }); }
18257
+ }
18258
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ExportDatagridService, decorators: [{
18259
+ type: Injectable
18260
+ }], ctorParameters: () => [] });
18261
+
18262
+ class ExportDatagridButtonComponent {
18263
+ constructor(exportService) {
18264
+ this.exportService = exportService;
18265
+ /* input signals */
18266
+ this.datagrid = input();
18267
+ this.datagridRef = input();
18268
+ this.exportTypesToShow = input();
18269
+ this.isBackendExport = input(false);
18270
+ this.exportTitlePrefix = input('export-datagrid');
18271
+ this.exportButtonPosition = input('right');
18272
+ this.possibleExportTypes = signal([ExportTypeEnum.ALL]);
18273
+ this.exportButtonText = input('EXPORT');
18274
+ this.destroy$ = new Subject();
18275
+ /* outputs */
18276
+ this.backendExport = new EventEmitter();
18277
+ this.exportTypes = [
18278
+ { type: ExportTypeEnum.ALL, value: 'All entries' },
18279
+ { type: ExportTypeEnum.FILTERED, value: 'Filtered entries' },
18280
+ { type: ExportTypeEnum.SELECTED, value: 'Selected entries' },
18281
+ ];
18282
+ this.exportTypesFiltered = computed(() => {
18283
+ let exportTypesToShowVal = this.exportTypesToShow();
18284
+ if (!exportTypesToShowVal || exportTypesToShowVal.length === 0) {
18285
+ exportTypesToShowVal = this.exportTypes;
18286
+ }
18287
+ return exportTypesToShowVal
18288
+ .filter(showType => this.possibleExportTypes().some(et => et === showType.type))
18289
+ .map(showType => {
18290
+ const defaultType = this.exportTypes.find(et => et.type === showType.type);
18291
+ return {
18292
+ type: showType.type,
18293
+ value: showType.value != null ? showType.value : defaultType?.value,
18294
+ };
18295
+ });
18296
+ });
18297
+ effect(() => {
18298
+ const datagrid = this.datagrid();
18299
+ if (!datagrid) {
18300
+ return undefined;
18301
+ }
18302
+ this.destroy$.next();
18303
+ datagrid.refresh.pipe(delay(0), takeUntil(this.destroy$)).subscribe(dgState => {
18304
+ const hasFilter = dgState.filters && dgState.filters.length > 0;
18305
+ this.updateExportType(ExportTypeEnum.FILTERED, hasFilter, this.exportTypesToShow() || this.exportTypes);
18306
+ });
18307
+ datagrid.selectedChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
18308
+ const hasSelection = datagrid.selection.current.length > 0;
18309
+ this.updateExportType(ExportTypeEnum.SELECTED, hasSelection, this.exportTypesToShow() || this.exportTypes);
18310
+ });
18311
+ });
18312
+ }
18313
+ ngOnDestroy() {
18314
+ this.destroy$.next();
18315
+ this.destroy$.complete();
18316
+ }
18317
+ exportExcel(type) {
18318
+ if (!this.datagrid() || !this.datagridRef()) {
18319
+ return;
18320
+ }
18321
+ // Filter visible columns once
18322
+ const visibleColumns = this.datagrid().columns.filter(col => !col.isHidden);
18323
+ // Get header titles for visible columns only
15841
18324
  const headerRow = this.getColumnsTitle().map(title => ({
15842
18325
  value: title,
15843
18326
  type: 'string',
@@ -15922,6 +18405,131 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
15922
18405
  }]
15923
18406
  }] });
15924
18407
 
18408
+ /*
18409
+ * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
18410
+ * This software is released under MIT license.
18411
+ * The full license information can be found in LICENSE in the root directory of this project.
18412
+ */
18413
+ ClarityIcons.addIcons(copyToClipboardIcon, successStandardIcon);
18414
+ class ClrCopyToClipboard {
18415
+ constructor() {
18416
+ this.value = input.required();
18417
+ this.tooltipText = input('Copy to clipboard');
18418
+ this.hiddenUntilHovered = input(false);
18419
+ this.showCopiedIcon = false;
18420
+ this.parentHovered = false;
18421
+ this.tooltipSize = 'md';
18422
+ this.tooltipPosition = 'bottom-right';
18423
+ this.cdr = inject(ChangeDetectorRef);
18424
+ this.elementRef = inject(ElementRef);
18425
+ this.resetTimeout = null;
18426
+ }
18427
+ ngOnInit() {
18428
+ const newSize = this.tooltipText && this.tooltipText().length < 15 ? 'sm' : 'md';
18429
+ if (this.tooltipSize !== newSize) {
18430
+ this.tooltipSize = newSize;
18431
+ this.cdr.markForCheck();
18432
+ }
18433
+ this.updateTooltipPosition();
18434
+ }
18435
+ ngAfterViewInit() {
18436
+ this.updateTooltipPosition();
18437
+ this.resizeListener = () => this.updateTooltipPosition();
18438
+ window.addEventListener('resize', this.resizeListener);
18439
+ if (this.hiddenUntilHovered()) {
18440
+ this.setupParentHoverListeners();
18441
+ }
18442
+ }
18443
+ ngOnDestroy() {
18444
+ if (this.resetTimeout) {
18445
+ clearTimeout(this.resetTimeout);
18446
+ this.resetTimeout = null;
18447
+ }
18448
+ if (this.resizeListener) {
18449
+ window.removeEventListener('resize', this.resizeListener);
18450
+ }
18451
+ this.teardownParentHoverListeners();
18452
+ }
18453
+ /**
18454
+ * Called when the clipboard copy operation completes.
18455
+ * @param success Whether the copy was successful
18456
+ */
18457
+ onCopied(success) {
18458
+ if (!success) {
18459
+ return;
18460
+ }
18461
+ if (this.resetTimeout) {
18462
+ clearTimeout(this.resetTimeout);
18463
+ }
18464
+ this.showCopiedIcon = true;
18465
+ this.cdr.markForCheck();
18466
+ this.resetTimeout = setTimeout(() => {
18467
+ this.showCopiedIcon = false;
18468
+ this.resetTimeout = null;
18469
+ this.cdr.markForCheck();
18470
+ }, 1000);
18471
+ }
18472
+ setupParentHoverListeners() {
18473
+ const parent = this.elementRef.nativeElement.parentElement;
18474
+ if (!parent) {
18475
+ return;
18476
+ }
18477
+ this.parentEnterListener = () => {
18478
+ this.parentHovered = true;
18479
+ this.cdr.markForCheck();
18480
+ };
18481
+ this.parentLeaveListener = () => {
18482
+ this.parentHovered = false;
18483
+ this.cdr.markForCheck();
18484
+ };
18485
+ parent.addEventListener('mouseenter', this.parentEnterListener);
18486
+ parent.addEventListener('mouseleave', this.parentLeaveListener);
18487
+ }
18488
+ teardownParentHoverListeners() {
18489
+ const parent = this.elementRef.nativeElement.parentElement;
18490
+ if (!parent) {
18491
+ return;
18492
+ }
18493
+ if (this.parentEnterListener) {
18494
+ parent.removeEventListener('mouseenter', this.parentEnterListener);
18495
+ }
18496
+ if (this.parentLeaveListener) {
18497
+ parent.removeEventListener('mouseleave', this.parentLeaveListener);
18498
+ }
18499
+ }
18500
+ updateTooltipPosition() {
18501
+ // Use double requestAnimationFrame to ensure CSS grid layout is complete
18502
+ requestAnimationFrame(() => {
18503
+ requestAnimationFrame(() => {
18504
+ const element = this.elementRef.nativeElement;
18505
+ const rect = element.getBoundingClientRect();
18506
+ const tooltipWidth = 200;
18507
+ const rightSpaceAvailable = window.innerWidth - rect.right;
18508
+ const newPosition = rightSpaceAvailable < tooltipWidth + 24 ? 'bottom-left' : 'bottom-right';
18509
+ if (this.tooltipPosition !== newPosition) {
18510
+ this.tooltipPosition = newPosition;
18511
+ this.cdr.markForCheck();
18512
+ }
18513
+ });
18514
+ });
18515
+ }
18516
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrCopyToClipboard, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
18517
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.18", type: ClrCopyToClipboard, isStandalone: true, selector: "clr-copy-to-clipboard", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, tooltipText: { classPropertyName: "tooltipText", publicName: "tooltipText", isSignal: true, isRequired: false, transformFunction: null }, hiddenUntilHovered: { classPropertyName: "hiddenUntilHovered", publicName: "hiddenUntilHovered", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.hidden-until-hovered": "hiddenUntilHovered()", "class.parent-hovered": "parentHovered" } }, ngImport: i0, template: "<clr-tooltip>\n <cds-icon\n clrTooltipTrigger\n class=\"copy-to-clipboard-icon\"\n [cdkCopyToClipboard]=\"value()\"\n (cdkCopyToClipboardCopied)=\"onCopied($event)\"\n [attr.shape]=\"showCopiedIcon ? 'success-standard' : 'copy-to-clipboard'\"\n [ngClass]=\"{ 'attribute-was-copied-color': showCopiedIcon }\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"$event.stopPropagation()\"\n ></cds-icon>\n <clr-tooltip-content [clrSize]=\"tooltipSize\" [clrPosition]=\"tooltipPosition\" *clrIfOpen\n >{{ tooltipText() }}</clr-tooltip-content\n >\n</clr-tooltip>\n", styles: [":host,.tooltip{display:flex;align-items:center}.copy-to-clipboard-icon{cursor:pointer;color:var(--cds-alias-status-disabled-tint)!important}.copy-to-clipboard-icon:hover{color:var(--cds-alias-typography-link-color)!important}.attribute-was-copied-color{color:var(--cds-alias-status-success)!important}:host(.hidden-until-hovered){visibility:hidden;opacity:0;transition:opacity .15s ease,visibility .15s ease}:host(.hidden-until-hovered.parent-hovered),:host(.hidden-until-hovered:focus-within){visibility:visible;opacity:1}.copy-to-clipboard-parent{display:flex;flex-direction:row;align-items:center;gap:.25rem}\n"], dependencies: [{ kind: "directive", type: CdkCopyToClipboard, selector: "[cdkCopyToClipboard]", inputs: ["cdkCopyToClipboard", "cdkCopyToClipboardAttempts"], outputs: ["cdkCopyToClipboardCopied"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ClrIconModule }, { kind: "directive", type: i1$1.CdsIconCustomTag, selector: "cds-icon" }, { kind: "ngmodule", type: ClrTooltipModule }, { kind: "component", type: i1$1.ClrTooltip, selector: "clr-tooltip" }, { kind: "directive", type: i1$1.ClrTooltipTrigger, selector: "[clrTooltipTrigger]" }, { kind: "component", type: i1$1.ClrTooltipContent, selector: "clr-tooltip-content", inputs: ["id", "clrPosition", "clrSize"] }, { kind: "directive", type: i1$1.ClrIfOpen, selector: "[clrIfOpen]", inputs: ["clrIfOpen"], outputs: ["clrIfOpenChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
18518
+ }
18519
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrCopyToClipboard, decorators: [{
18520
+ type: Component,
18521
+ args: [{ selector: 'clr-copy-to-clipboard', imports: [CdkCopyToClipboard, NgClass, ClrIconModule, ClrTooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, host: {
18522
+ '[class.hidden-until-hovered]': 'hiddenUntilHovered()',
18523
+ '[class.parent-hovered]': 'parentHovered',
18524
+ }, template: "<clr-tooltip>\n <cds-icon\n clrTooltipTrigger\n class=\"copy-to-clipboard-icon\"\n [cdkCopyToClipboard]=\"value()\"\n (cdkCopyToClipboardCopied)=\"onCopied($event)\"\n [attr.shape]=\"showCopiedIcon ? 'success-standard' : 'copy-to-clipboard'\"\n [ngClass]=\"{ 'attribute-was-copied-color': showCopiedIcon }\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"$event.stopPropagation()\"\n ></cds-icon>\n <clr-tooltip-content [clrSize]=\"tooltipSize\" [clrPosition]=\"tooltipPosition\" *clrIfOpen\n >{{ tooltipText() }}</clr-tooltip-content\n >\n</clr-tooltip>\n", styles: [":host,.tooltip{display:flex;align-items:center}.copy-to-clipboard-icon{cursor:pointer;color:var(--cds-alias-status-disabled-tint)!important}.copy-to-clipboard-icon:hover{color:var(--cds-alias-typography-link-color)!important}.attribute-was-copied-color{color:var(--cds-alias-status-success)!important}:host(.hidden-until-hovered){visibility:hidden;opacity:0;transition:opacity .15s ease,visibility .15s ease}:host(.hidden-until-hovered.parent-hovered),:host(.hidden-until-hovered:focus-within){visibility:visible;opacity:1}.copy-to-clipboard-parent{display:flex;flex-direction:row;align-items:center;gap:.25rem}\n"] }]
18525
+ }] });
18526
+
18527
+ /*
18528
+ * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
18529
+ * This software is released under MIT license.
18530
+ * The full license information can be found in LICENSE in the root directory of this project.
18531
+ */
18532
+
15925
18533
  /*
15926
18534
  * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
15927
18535
  * This software is released under MIT license.
@@ -16182,88 +18790,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
16182
18790
  args: ['class.hidden']
16183
18791
  }] } });
16184
18792
 
16185
- /*
16186
- * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
16187
- * This software is released under MIT license.
16188
- * The full license information can be found in LICENSE in the root directory of this project.
16189
- */
16190
- ClarityIcons.addIcons(copyToClipboardIcon, successStandardIcon);
16191
- class ClrSummaryItemValueCopyButton {
16192
- constructor() {
16193
- this.value = input.required();
16194
- this.tooltipText = input('Copy to clipboard');
16195
- this.showCopiedIcon = false;
16196
- this.tooltipSize = 'md';
16197
- this.tooltipPosition = 'bottom-right';
16198
- this.cdr = inject(ChangeDetectorRef);
16199
- this.elementRef = inject(ElementRef);
16200
- this.resetTimeout = null;
16201
- }
16202
- ngOnInit() {
16203
- const newSize = this.tooltipText && this.tooltipText().length < 15 ? 'sm' : 'md';
16204
- if (this.tooltipSize !== newSize) {
16205
- this.tooltipSize = newSize;
16206
- this.cdr.markForCheck();
16207
- }
16208
- this.updateTooltipPosition();
16209
- }
16210
- ngAfterViewInit() {
16211
- this.updateTooltipPosition();
16212
- this.resizeListener = () => this.updateTooltipPosition();
16213
- window.addEventListener('resize', this.resizeListener);
16214
- }
16215
- ngOnDestroy() {
16216
- if (this.resetTimeout) {
16217
- clearTimeout(this.resetTimeout);
16218
- this.resetTimeout = null;
16219
- }
16220
- if (this.resizeListener) {
16221
- window.removeEventListener('resize', this.resizeListener);
16222
- }
16223
- }
16224
- /**
16225
- * Called when the clipboard copy operation completes.
16226
- * @param success Whether the copy was successful
16227
- */
16228
- onCopied(success) {
16229
- if (!success) {
16230
- return;
16231
- }
16232
- if (this.resetTimeout) {
16233
- clearTimeout(this.resetTimeout);
16234
- }
16235
- this.showCopiedIcon = true;
16236
- this.cdr.markForCheck();
16237
- this.resetTimeout = setTimeout(() => {
16238
- this.showCopiedIcon = false;
16239
- this.resetTimeout = null;
16240
- this.cdr.markForCheck();
16241
- }, 1000);
16242
- }
16243
- updateTooltipPosition() {
16244
- // Use double requestAnimationFrame to ensure CSS grid layout is complete
16245
- requestAnimationFrame(() => {
16246
- requestAnimationFrame(() => {
16247
- const element = this.elementRef.nativeElement;
16248
- const rect = element.getBoundingClientRect();
16249
- const tooltipWidth = 200;
16250
- const rightSpaceAvailable = window.innerWidth - rect.right;
16251
- const newPosition = rightSpaceAvailable < tooltipWidth + 24 ? 'bottom-left' : 'bottom-right';
16252
- if (this.tooltipPosition !== newPosition) {
16253
- this.tooltipPosition = newPosition;
16254
- this.cdr.markForCheck();
16255
- }
16256
- });
16257
- });
16258
- }
16259
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrSummaryItemValueCopyButton, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
16260
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.18", type: ClrSummaryItemValueCopyButton, isStandalone: true, selector: "clr-summary-area-value-copy-button", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, tooltipText: { classPropertyName: "tooltipText", publicName: "tooltipText", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<clr-tooltip>\n <cds-icon\n clrTooltipTrigger\n class=\"summary-item-copy-value-icon\"\n [cdkCopyToClipboard]=\"value()\"\n (cdkCopyToClipboardCopied)=\"onCopied($event)\"\n [attr.shape]=\"showCopiedIcon ? 'success-standard' : 'copy-to-clipboard'\"\n [ngClass]=\"{ 'attribute-was-copied-color': showCopiedIcon }\"\n ></cds-icon>\n <clr-tooltip-content [clrSize]=\"tooltipSize\" [clrPosition]=\"tooltipPosition\" *clrIfOpen\n >{{ tooltipText() }}</clr-tooltip-content\n >\n</clr-tooltip>\n", styles: [":host,.tooltip{display:flex;align-items:center}.summary-item-copy-value-icon{cursor:pointer;color:var(--cds-alias-status-disabled-tint)!important}.summary-item-copy-value-icon:hover{color:var(--cds-alias-typography-link-color)!important}.attribute-was-copied-color{color:var(--cds-alias-status-success)!important}\n"], dependencies: [{ kind: "directive", type: CdkCopyToClipboard, selector: "[cdkCopyToClipboard]", inputs: ["cdkCopyToClipboard", "cdkCopyToClipboardAttempts"], outputs: ["cdkCopyToClipboardCopied"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ClrIconModule }, { kind: "directive", type: i1$1.CdsIconCustomTag, selector: "cds-icon" }, { kind: "ngmodule", type: ClrTooltipModule }, { kind: "component", type: i1$1.ClrTooltip, selector: "clr-tooltip" }, { kind: "directive", type: i1$1.ClrTooltipTrigger, selector: "[clrTooltipTrigger]" }, { kind: "component", type: i1$1.ClrTooltipContent, selector: "clr-tooltip-content", inputs: ["id", "clrPosition", "clrSize"] }, { kind: "directive", type: i1$1.ClrIfOpen, selector: "[clrIfOpen]", inputs: ["clrIfOpen"], outputs: ["clrIfOpenChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16261
- }
16262
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrSummaryItemValueCopyButton, decorators: [{
16263
- type: Component,
16264
- args: [{ selector: 'clr-summary-area-value-copy-button', imports: [CdkCopyToClipboard, NgClass, ClrIconModule, ClrTooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<clr-tooltip>\n <cds-icon\n clrTooltipTrigger\n class=\"summary-item-copy-value-icon\"\n [cdkCopyToClipboard]=\"value()\"\n (cdkCopyToClipboardCopied)=\"onCopied($event)\"\n [attr.shape]=\"showCopiedIcon ? 'success-standard' : 'copy-to-clipboard'\"\n [ngClass]=\"{ 'attribute-was-copied-color': showCopiedIcon }\"\n ></cds-icon>\n <clr-tooltip-content [clrSize]=\"tooltipSize\" [clrPosition]=\"tooltipPosition\" *clrIfOpen\n >{{ tooltipText() }}</clr-tooltip-content\n >\n</clr-tooltip>\n", styles: [":host,.tooltip{display:flex;align-items:center}.summary-item-copy-value-icon{cursor:pointer;color:var(--cds-alias-status-disabled-tint)!important}.summary-item-copy-value-icon:hover{color:var(--cds-alias-typography-link-color)!important}.attribute-was-copied-color{color:var(--cds-alias-status-success)!important}\n"] }]
16265
- }] });
16266
-
16267
18793
  /*
16268
18794
  * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
16269
18795
  * This software is released under MIT license.
@@ -16437,7 +18963,7 @@ class ClrSummaryItem {
16437
18963
  const element = node;
16438
18964
  const tagName = element.tagName?.toLowerCase();
16439
18965
  // Skip our own components and internal elements
16440
- if (tagName === 'clr-summary-item-value' || tagName === 'clr-summary-area-value-copy-button') {
18966
+ if (tagName === 'clr-summary-item-value' || tagName === 'clr-copy-to-clipboard') {
16441
18967
  return false;
16442
18968
  }
16443
18969
  // Skip internal elements by class
@@ -16454,11 +18980,11 @@ class ClrSummaryItem {
16454
18980
  });
16455
18981
  }
16456
18982
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrSummaryItem, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
16457
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ClrSummaryItem, isStandalone: true, selector: "clr-summary-item", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, warning: { classPropertyName: "warning", publicName: "warning", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, editConfig: { classPropertyName: "editConfig", publicName: "editConfig", isSignal: true, isRequired: false, transformFunction: null }, showOnEmptyValue: { classPropertyName: "showOnEmptyValue", publicName: "showOnEmptyValue", isSignal: true, isRequired: false, transformFunction: null }, valueCopyable: { classPropertyName: "valueCopyable", publicName: "valueCopyable", isSignal: true, isRequired: false, transformFunction: null }, copyButtonTooltip: { classPropertyName: "copyButtonTooltip", publicName: "copyButtonTooltip", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "valueChildren", predicate: ClrSummaryItemValue, descendants: true }], viewQueries: [{ propertyName: "template", first: true, predicate: ["itemTemplate"], descendants: true, static: true }, { propertyName: "valuesContainer", first: true, predicate: ["valuesContainer"], descendants: true }], ngImport: i0, template: "<ng-template #itemTemplate>\n <div\n class=\"summary-item\"\n [class.hidden]=\"\n !hasProjectedContent && !hasLoading && !hasError && !hasWarning && !showEditButton && !showOnEmptyValue()\n \"\n >\n <span class=\"summary-item-label\">{{ label() }}:</span>\n <div class=\"summary-item-values\" #valuesContainer>\n @if (hasLoading) {\n <div class=\"summary-item-loading\">\n <span class=\"spinner spinner-inline\"></span>\n <span>{{ loadingText }}</span>\n </div>\n } @else if (hasError) {\n <clr-summary-item-value icon=\"error-standard\" class=\"error\"></clr-summary-item-value>\n <clr-summary-item-value\n [value]=\"errorText\"\n [clickable]=\"true\"\n (clicked)=\"errorClick()\"\n class=\"error\"\n ></clr-summary-item-value>\n } @else if (hasWarning) {\n <clr-summary-item-value icon=\"warning-standard\" class=\"warning\"></clr-summary-item-value>\n <clr-summary-item-value\n [value]=\"warningText\"\n [clickable]=\"true\"\n (clicked)=\"warningClick()\"\n class=\"warning\"\n ></clr-summary-item-value>\n } @else {\n <ng-content select=\"clr-summary-item-value\"></ng-content>\n @if (showCopyButton) {\n <clr-summary-area-value-copy-button\n [value]=\"copyableValue\"\n [tooltipText]=\"copyButtonTooltip()\"\n ></clr-summary-area-value-copy-button>\n }\n <ng-content></ng-content>\n @if (showEditButton) {\n <clr-summary-item-value [value]=\"editText\" [clickable]=\"true\" (clicked)=\"editClick()\"></clr-summary-item-value>\n } @else if (!hasProjectedContent) {\n <span class=\"value value-placeholder\">&mdash;</span>\n } }\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block;min-width:0;max-width:100%;overflow:hidden}.summary-item{display:flex;flex-direction:row;align-items:center;gap:.25rem;width:100%;min-width:0}.summary-item.hidden{display:none}.summary-item-label{flex-shrink:0;white-space:nowrap;font-weight:600}.summary-item-values{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center;gap:.25rem;flex:1 1 auto;min-width:0;max-width:100%;overflow:visible}clr-summary-area-value-copy-button{flex:0 0 auto}.summary-item-loading{display:flex;align-items:center}.summary-item-loading .loading-spinner{width:20px;height:20px;flex-shrink:0}.summary-item-loading .loading-text{color:var(--cds-alias-typography-color-300);white-space:nowrap}.value{display:inline-block;flex:1 1 auto;min-width:0;overflow:visible;text-overflow:ellipsis;white-space:nowrap}.value-link,.edit-link{color:var(--cds-alias-typography-link-color);text-decoration:underline;cursor:pointer}.value-link:visited,.edit-link:visited{color:var(--cds-alias-typography-link-color-visited)}.value-link:hover,.value-link:focus,.edit-link:hover,.edit-link:focus{color:var(--cds-alias-typography-link-color-hover);text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ClarityModule }, { kind: "component", type: ClrSummaryItemValue, selector: "clr-summary-item-value", inputs: ["value", "icon", "tooltip", "clickable"], outputs: ["clicked"] }, { kind: "component", type: ClrSummaryItemValueCopyButton, selector: "clr-summary-area-value-copy-button", inputs: ["value", "tooltipText"] }] }); }
18983
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ClrSummaryItem, isStandalone: true, selector: "clr-summary-item", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, warning: { classPropertyName: "warning", publicName: "warning", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, editConfig: { classPropertyName: "editConfig", publicName: "editConfig", isSignal: true, isRequired: false, transformFunction: null }, showOnEmptyValue: { classPropertyName: "showOnEmptyValue", publicName: "showOnEmptyValue", isSignal: true, isRequired: false, transformFunction: null }, valueCopyable: { classPropertyName: "valueCopyable", publicName: "valueCopyable", isSignal: true, isRequired: false, transformFunction: null }, copyButtonTooltip: { classPropertyName: "copyButtonTooltip", publicName: "copyButtonTooltip", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "valueChildren", predicate: ClrSummaryItemValue, descendants: true }], viewQueries: [{ propertyName: "template", first: true, predicate: ["itemTemplate"], descendants: true, static: true }, { propertyName: "valuesContainer", first: true, predicate: ["valuesContainer"], descendants: true }], ngImport: i0, template: "<ng-template #itemTemplate>\n <div\n class=\"summary-item\"\n [class.hidden]=\"\n !hasProjectedContent && !hasLoading && !hasError && !hasWarning && !showEditButton && !showOnEmptyValue()\n \"\n >\n <span class=\"summary-item-label\">{{ label() }}:</span>\n <div class=\"summary-item-values\" #valuesContainer>\n @if (hasLoading) {\n <div class=\"summary-item-loading\">\n <span class=\"spinner spinner-inline\"></span>\n <span>{{ loadingText }}</span>\n </div>\n } @else if (hasError) {\n <clr-summary-item-value icon=\"error-standard\" class=\"error\"></clr-summary-item-value>\n <clr-summary-item-value\n [value]=\"errorText\"\n [clickable]=\"true\"\n (clicked)=\"errorClick()\"\n class=\"error\"\n ></clr-summary-item-value>\n } @else if (hasWarning) {\n <clr-summary-item-value icon=\"warning-standard\" class=\"warning\"></clr-summary-item-value>\n <clr-summary-item-value\n [value]=\"warningText\"\n [clickable]=\"true\"\n (clicked)=\"warningClick()\"\n class=\"warning\"\n ></clr-summary-item-value>\n } @else {\n <ng-content select=\"clr-summary-item-value\"></ng-content>\n @if (showCopyButton) {\n <clr-copy-to-clipboard [value]=\"copyableValue\" [tooltipText]=\"copyButtonTooltip()\"></clr-copy-to-clipboard>\n }\n <ng-content></ng-content>\n @if (showEditButton) {\n <clr-summary-item-value [value]=\"editText\" [clickable]=\"true\" (clicked)=\"editClick()\"></clr-summary-item-value>\n } @else if (!hasProjectedContent) {\n <span class=\"value value-placeholder\">&mdash;</span>\n } }\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block;min-width:0;max-width:100%;overflow:hidden}.summary-item{display:flex;flex-direction:row;align-items:center;gap:.25rem;width:100%;min-width:0}.summary-item.hidden{display:none}.summary-item-label{flex-shrink:0;white-space:nowrap;font-weight:600}.summary-item-values{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center;gap:.25rem;flex:1 1 auto;min-width:0;max-width:100%;overflow:visible}clr-copy-to-clipboard{flex:0 0 auto}.summary-item-loading{display:flex;align-items:center}.summary-item-loading .loading-spinner{width:20px;height:20px;flex-shrink:0}.summary-item-loading .loading-text{color:var(--cds-alias-typography-color-300);white-space:nowrap}.value{display:inline-block;flex:1 1 auto;min-width:0;overflow:visible;text-overflow:ellipsis;white-space:nowrap}.value-link,.edit-link{color:var(--cds-alias-typography-link-color);text-decoration:underline;cursor:pointer}.value-link:visited,.edit-link:visited{color:var(--cds-alias-typography-link-color-visited)}.value-link:hover,.value-link:focus,.edit-link:hover,.edit-link:focus{color:var(--cds-alias-typography-link-color-hover);text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ClarityModule }, { kind: "component", type: ClrSummaryItemValue, selector: "clr-summary-item-value", inputs: ["value", "icon", "tooltip", "clickable"], outputs: ["clicked"] }, { kind: "component", type: ClrCopyToClipboard, selector: "clr-copy-to-clipboard", inputs: ["value", "tooltipText", "hiddenUntilHovered"] }] }); }
16458
18984
  }
16459
18985
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrSummaryItem, decorators: [{
16460
18986
  type: Component,
16461
- args: [{ selector: 'clr-summary-item', standalone: true, imports: [CommonModule, ClarityModule, ClrSummaryItemValue, ClrSummaryItemValueCopyButton], template: "<ng-template #itemTemplate>\n <div\n class=\"summary-item\"\n [class.hidden]=\"\n !hasProjectedContent && !hasLoading && !hasError && !hasWarning && !showEditButton && !showOnEmptyValue()\n \"\n >\n <span class=\"summary-item-label\">{{ label() }}:</span>\n <div class=\"summary-item-values\" #valuesContainer>\n @if (hasLoading) {\n <div class=\"summary-item-loading\">\n <span class=\"spinner spinner-inline\"></span>\n <span>{{ loadingText }}</span>\n </div>\n } @else if (hasError) {\n <clr-summary-item-value icon=\"error-standard\" class=\"error\"></clr-summary-item-value>\n <clr-summary-item-value\n [value]=\"errorText\"\n [clickable]=\"true\"\n (clicked)=\"errorClick()\"\n class=\"error\"\n ></clr-summary-item-value>\n } @else if (hasWarning) {\n <clr-summary-item-value icon=\"warning-standard\" class=\"warning\"></clr-summary-item-value>\n <clr-summary-item-value\n [value]=\"warningText\"\n [clickable]=\"true\"\n (clicked)=\"warningClick()\"\n class=\"warning\"\n ></clr-summary-item-value>\n } @else {\n <ng-content select=\"clr-summary-item-value\"></ng-content>\n @if (showCopyButton) {\n <clr-summary-area-value-copy-button\n [value]=\"copyableValue\"\n [tooltipText]=\"copyButtonTooltip()\"\n ></clr-summary-area-value-copy-button>\n }\n <ng-content></ng-content>\n @if (showEditButton) {\n <clr-summary-item-value [value]=\"editText\" [clickable]=\"true\" (clicked)=\"editClick()\"></clr-summary-item-value>\n } @else if (!hasProjectedContent) {\n <span class=\"value value-placeholder\">&mdash;</span>\n } }\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block;min-width:0;max-width:100%;overflow:hidden}.summary-item{display:flex;flex-direction:row;align-items:center;gap:.25rem;width:100%;min-width:0}.summary-item.hidden{display:none}.summary-item-label{flex-shrink:0;white-space:nowrap;font-weight:600}.summary-item-values{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center;gap:.25rem;flex:1 1 auto;min-width:0;max-width:100%;overflow:visible}clr-summary-area-value-copy-button{flex:0 0 auto}.summary-item-loading{display:flex;align-items:center}.summary-item-loading .loading-spinner{width:20px;height:20px;flex-shrink:0}.summary-item-loading .loading-text{color:var(--cds-alias-typography-color-300);white-space:nowrap}.value{display:inline-block;flex:1 1 auto;min-width:0;overflow:visible;text-overflow:ellipsis;white-space:nowrap}.value-link,.edit-link{color:var(--cds-alias-typography-link-color);text-decoration:underline;cursor:pointer}.value-link:visited,.edit-link:visited{color:var(--cds-alias-typography-link-color-visited)}.value-link:hover,.value-link:focus,.edit-link:hover,.edit-link:focus{color:var(--cds-alias-typography-link-color-hover);text-decoration:underline}\n"] }]
18987
+ args: [{ selector: 'clr-summary-item', standalone: true, imports: [CommonModule, ClarityModule, ClrSummaryItemValue, ClrCopyToClipboard], template: "<ng-template #itemTemplate>\n <div\n class=\"summary-item\"\n [class.hidden]=\"\n !hasProjectedContent && !hasLoading && !hasError && !hasWarning && !showEditButton && !showOnEmptyValue()\n \"\n >\n <span class=\"summary-item-label\">{{ label() }}:</span>\n <div class=\"summary-item-values\" #valuesContainer>\n @if (hasLoading) {\n <div class=\"summary-item-loading\">\n <span class=\"spinner spinner-inline\"></span>\n <span>{{ loadingText }}</span>\n </div>\n } @else if (hasError) {\n <clr-summary-item-value icon=\"error-standard\" class=\"error\"></clr-summary-item-value>\n <clr-summary-item-value\n [value]=\"errorText\"\n [clickable]=\"true\"\n (clicked)=\"errorClick()\"\n class=\"error\"\n ></clr-summary-item-value>\n } @else if (hasWarning) {\n <clr-summary-item-value icon=\"warning-standard\" class=\"warning\"></clr-summary-item-value>\n <clr-summary-item-value\n [value]=\"warningText\"\n [clickable]=\"true\"\n (clicked)=\"warningClick()\"\n class=\"warning\"\n ></clr-summary-item-value>\n } @else {\n <ng-content select=\"clr-summary-item-value\"></ng-content>\n @if (showCopyButton) {\n <clr-copy-to-clipboard [value]=\"copyableValue\" [tooltipText]=\"copyButtonTooltip()\"></clr-copy-to-clipboard>\n }\n <ng-content></ng-content>\n @if (showEditButton) {\n <clr-summary-item-value [value]=\"editText\" [clickable]=\"true\" (clicked)=\"editClick()\"></clr-summary-item-value>\n } @else if (!hasProjectedContent) {\n <span class=\"value value-placeholder\">&mdash;</span>\n } }\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block;min-width:0;max-width:100%;overflow:hidden}.summary-item{display:flex;flex-direction:row;align-items:center;gap:.25rem;width:100%;min-width:0}.summary-item.hidden{display:none}.summary-item-label{flex-shrink:0;white-space:nowrap;font-weight:600}.summary-item-values{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center;gap:.25rem;flex:1 1 auto;min-width:0;max-width:100%;overflow:visible}clr-copy-to-clipboard{flex:0 0 auto}.summary-item-loading{display:flex;align-items:center}.summary-item-loading .loading-spinner{width:20px;height:20px;flex-shrink:0}.summary-item-loading .loading-text{color:var(--cds-alias-typography-color-300);white-space:nowrap}.value{display:inline-block;flex:1 1 auto;min-width:0;overflow:visible;text-overflow:ellipsis;white-space:nowrap}.value-link,.edit-link{color:var(--cds-alias-typography-link-color);text-decoration:underline;cursor:pointer}.value-link:visited,.edit-link:visited{color:var(--cds-alias-typography-link-color-visited)}.value-link:hover,.value-link:focus,.edit-link:hover,.edit-link:focus{color:var(--cds-alias-typography-link-color-hover);text-decoration:underline}\n"] }]
16462
18988
  }], propDecorators: { template: [{
16463
18989
  type: ViewChild,
16464
18990
  args: ['itemTemplate', { static: true }]
@@ -16949,5 +19475,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
16949
19475
  * Generated bundle index. Do not edit.
16950
19476
  */
16951
19477
 
16952
- export { ACShape, AcceptanceDateShape, AcceptedBrands, AccessoryPartsShape, AudiBrandShape, AwardWinnerPremiumShape, BIG_ENDIAN, BlocksGroupForwardShape, BundleForwardShape, BusinessCustomersCommercialShape, BusinessCustomersPrivateShape, BusinessPartnerWithCar, CLR_BLANK_OPTION, CONTENT_PROVIDER, CalculatorForwardShape, CaliforniaServiceShape, CampaignOutdatedShape, CampaignShape, CarOffSite, CarOnSite, CircleFilled, CircleHalfFilled, CircleQuarterFilled, CircleThreeQuartersFilled, ClrActionPanel, ClrActionPanelContainer, ClrActionPanelContainerContent, ClrActionPanelContainerFooter, ClrActionPanelModule, ClrActiveNotification, ClrAddonsLabel, ClrAddonsModule, ClrAutocompleteOff, ClrAutocompleteOffModule, ClrBackButton, ClrBackButtonModule, ClrBrandAvatar, ClrBrandAvatarModule, ClrBreadcrumb, ClrBreadcrumbModule, ClrBreadcrumbService, ClrCollapseExpandSection, ClrCollapseExpandSectionModule, ClrContentPanel, ClrContentPanelContainer, ClrContentPanelContainerContent, ClrContentPanelContainerFooter, ClrContentPanelModule, ClrContentRef, ClrControlEnterSubmitDirective, ClrDataListPredefinedValidatorDirective, ClrDataListValidatorModule, ClrDataListValidators, ClrDatagridColumnReorderModule, ClrDatagridStatePersistenceModule, ClrDateFilterComponent, ClrDateFilterModule, ClrDateTimeContainer, ClrDateTimeModule, ClrDaterangeMaxValidator, ClrDaterangeMinValidator, ClrDaterangeOrderValidator, ClrDaterangeRequiredValidator, ClrDaterangepickerContainerComponent, ClrDaterangepickerDirective, ClrDaterangepickerModule, ClrDotPager, ClrDotPagerModule, ClrDropdownOverflowDirective, ClrDropdownOverflowModule, ClrEnumFilterComponent, ClrEnumFilterModule, ClrExportDatagridButtonModule, ClrFlowBar, ClrFlowBarModule, ClrFocusFirstInvalidFieldDirective, ClrFormModule, ClrGenericQuickList, ClrGenericQuickListModule, ClrHistory, ClrHistoryModule, ClrHistoryPinned, ClrHistoryService, ClrIconAvatar, ClrIconAvatarModule, ClrIfDaterangeErrorDirective, ClrIfWarning, ClrIfWarningModule, ClrLetterAvatar, ClrLetterAvatarModule, ClrLocationBarModule, ClrMainNavGroup, ClrMainNavGroupItem, ClrMainNavGroupModule, ClrMaxNumeric, ClrMinNumeric, ClrMultilingualInput, ClrMultilingualInputValidators, ClrMultilingualModule, ClrMultilingualSelector, ClrMultilingualTextarea, ClrNotification, ClrNotificationModule, ClrNotificationRef, ClrNotificationService, ClrNumericField, ClrNumericFieldModule, ClrNumericFieldValidators, ClrPagedSearchResultList, ClrPagedSearchResultListModule, ClrPager, ClrPagerModule, ClrProgressSpinnerComponent, ClrProgressSpinnerModule, ClrQuickList, ClrQuickListModule, ClrReadonlyDirective, ClrReadonlyDirectiveModule, ClrRequiredAllMultilang, ClrRequiredOneMultilang, ClrSearchField, ClrSearchFieldModule, ClrSignpostAddonComponent, ClrSignpostAddonModule, ClrSummaryArea, ClrSummaryAreaStateService, ClrSummaryAreaToggle, ClrSummaryItem, ClrSummaryItemValue, ClrSummaryItemValueCopyButton, ClrTimeInput, ClrTreetable, ClrTreetableActionOverflow, ClrTreetableCell, ClrTreetableColumn, ClrTreetableFilter, ClrTreetableModule, ClrTreetablePlaceholder, ClrTreetableRow, ClrTreetableSortOrder, ClrTreetableStringFilter, ClrTreetableTreeNode, ClrViewEditSection, ClrViewEditSectionModule, ColumnHiddenStatePersistenceDirective, CompletedByDateShape, CupraBrandShape, Customer, CustomerVip, CustomerVipCollection, CustomerWaiting, CustomerWaitingCollection, DATE, DELIMITER_REGEX, DWABrandShape, DatagridColumnReorderDirective, DatagridFieldDirective, DayModel, DeliveryDate, DieselShape, DollarBillForwardShape, DollarBillPartialShape, DynamicCellContentComponent, EnergyShape, ExportDatagridButtonComponent, ExportDatagridService, ExportTypeEnum, ExternalPartForwardShape, FirstRegistrationDate, GasCarsServiceShape, GasShape, HISTORY_NOTIFICATION_URL_PROVIDER, HISTORY_PROVIDER, HISTORY_TOKEN, HistoryProvider, InternalPartForwardShape, InvoiceReadyShape, InvoiceRecipient, InvoiceShape, ItemsForwardShape, ItemsReceiveShape, LITTLE_ENDIAN, LITTLE_ENDIAN_REGEX, LocationBarComponent, LocationBarContentProvider, LocationBarNode, LogoCommissionModule, LogoCommissionModuleFavIcon, LogoCommissionModuleNegative, LogoCommissionModuleNegativeFavIcon, LogoCostApproval, LogoCostApprovalFavIcon, LogoCostApprovalNegative, LogoCostApprovalNegativeFavIcon, LogoCostControlling, LogoCostControllingFavIcon, LogoCostControllingNegative, LogoCostControllingNegativeFavIcon, LogoDigitalServiceReception, LogoDigitalServiceReceptionFavIcon, LogoDigitalServiceReceptionNegative, LogoDigitalServiceReceptionNegativeFavIcon, LogoDocFlow, LogoDocFlowFavIcon, LogoDocFlowNegative, LogoDocFlowNegativeFavIcon, LogoDocScan, LogoDocScanFavIcon, LogoDocScanNegative, LogoDocScanNegativeFavIcon, LogoDocStore, LogoDocStoreFavIcon, LogoDocStoreNegative, LogoDocStoreNegativeFavIcon, LogoEBilling, LogoEBillingFavIcon, LogoEBillingNegative, LogoEBillingNegativeFavIcon, LogoEPayment, LogoEPaymentFavIcon, LogoEPaymentNegative, LogoEPaymentNegativeFavIcon, LogoMobilityPlanner, LogoMobilityPlannerFavIcon, LogoMobilityPlannerNegative, LogoMobilityPlannerNegativeFavIcon, LogoPartsMobile, LogoPartsMobileFavIcon, LogoPartsMobileNegative, LogoPartsMobileNegativeFavIcon, LogoSBO, LogoSBOFavIcon, LogoSBONegative, LogoSBONegativeFavIcon, LogoServiceCube, LogoServiceCubeFavIcon, LogoServiceCubeNegative, LogoServiceCubeNegativeFavIcon, LogoWCP, LogoWCPFavIcon, LogoWCPNegative, LogoWCPNegativeFavIcon, LogoWorkshopOrderTracker, LogoWorkshopOrderTrackerFavIcon, LogoWorkshopOrderTrackerNegative, LogoWorkshopOrderTrackerNegativeFavIcon, MIDDLE_ENDIAN, MIDDLE_ENDIAN_REGEX, MONTH, Mechanic, NewCarUtilityVehicleShape, NodeId, Number0, Number1, Number10, Number11, Number12, Number13, Number14, Number15, Number16, Number17, Number18, Number19, Number2, Number20, Number3, Number4, Number5, Number6, Number7, Number8, Number9, OrderShape, OrderStatusShape, PaintMaterialForwardShape, PaintMaterialShape, ParkingLocation, PartAvailabilityInfoShape, PartAvailabilityNoShape, PartAvailabilityUnknownShape, PartAvailabilityWarningShape, PartAvailabilityYesShape, PartIdenticalPredecessorShape, PartIdenticalShape, PartIdenticalSuccessorShape, PartIdenticalSuccpredecessorShape, PartNonStockForwardShape, PartPredecessorShape, PartSuccessorPredecessorShape, PartSuccessorShape, PartsChangelocation, PartsForwardShape, PartsInventory, PartsNonStockShape, PartsPicking, PartsPickingPlus, PartsReceiving, PartsShape, PlusServiceShape, PopoverPositions, PorscheBrandShape, PriceTypeSwitchShape, RepeatRepairCollection, RepeatRepairShape, ReplacementVehicleCollection, ReplacementVehicleShape, ReturnDateShape, SEPARATOR_TEXT_DEFAULT, SeatBrandShape, ServiceAdvisor, SkodaBrandShape, Sort, StatePersistenceKeyDirective, TRANSLATIONS, TaskAndAppointment, TextForward, TimeModel, TopcardShape, TouaregServiceShape, TreetableCellRenderer, TreetableDataStateService, TreetableHeaderRenderer, TreetableItemsDirective, TreetableMainRenderer, TreetableRowRenderer, USER_INPUT_REGEX, VWBrandShape, VWNBrandShape, VWShape, VehicleConversionShape, VinShape, VsfSearchShape, VsfSearchShape48, WCPShape, WrenchForward, YEAR, acceleration, accelerationIcon, acceptanceDateIcon, accessories, accessoriesIcon, accessoryPartsIcon, adblueAppIcon, adblue_app, add, addIcon, airConditionerIcon, air_conditioning, alert, alertFilledAppIcon, alertIcon, alertNotificationFilledAppIcon, alert_filled_app, alert_notification_filled_app, allIcons, ambientLightAppIcon, ambient_light_app, amplifier, amplifierIcon, appConnectAppIcon, app_connect_app, archive, archiveIcon, arrowDownIcon, arrowLeftAlignedAppIcon, arrowLeftIcon, arrowRightIcon, arrowSliderAppIcon, arrowUpIcon, arrow_down, arrow_left, arrow_left_aligned_app, arrow_right, arrow_slider_app, arrow_up, attachment, attachmentIcon, audiBrandIcon, authentPlugChargeAppIcon, authentQrAppIcon, authentRfidAppIcon, authentTouchidAppIcon, authent_plug_charge_app, authent_qr_app, authent_rfid_app, authent_touch_id_app, automaticTempAppIcon, automatic_temp_app, awardWinnerPremiumIcon, award_winner_premium, back, backIcon, battery, batteryIcon, batterySocChargingAppIcon, batterySocDepartureAppIcon, batterySocDestinationAppIcon, battery_soc_charging_app, battery_soc_departure_app, battery_soc_destination_app, bin, binIcon, blocksGroupForwardIcon, bluetooth, bluetoothIcon, bookmark, bookmarkFilledIcon, bookmarkIcon, bookmark_filled, brakeAppIcon, brake_app, brochure, brochureIcon, bulletpointAppIcon, bulletpoint_app, bundleForwardIcon, businessCustomersCommercialIcon, businessCustomersPrivateIcon, businessPartnerWithCarIcon, business_customers_commercial, business_customers_private, calc, calcIcon, calculatorForwardIcon, calendar, calendarCustomIcon, californiaServiceIcon, californiaSpecialistIcon, california_specialist, cameraScanIcon, camera_scan, campaignIcon, campaignOutdatedIcon, carDocumentsIcon, carErrorAppIcon, carInsuranceIcon, carOffSite, carOnSite, carPickupServiceIcon, carPlusIcon, carSettingsIcon, carVerifiedAppIcon, carWashIcon, carWheelAppIcon, car_documents, car_error_app, car_insurance, car_pickup_service, car_plus, car_settings, car_verified_app, car_wheel_app, carwash, certifiedRepairIcon, certifiedRetailerIcon, certified_repair, certified_retailer, challengeAppIcon, challenge_app, charging, chargingIcon, chargingPduAppIcon, chargingStationIcon, chargingTarifOverviewAppIcon, charging_pdu_app, charging_station, charging_tarif_overview_app, chat, chatAppIcon, chatIcon, chat_app, checkboxCheckedAppIcon, checkboxCheckedIcon, checkboxUncheckedAppIcon, checkboxUncheckedIcon, checkbox_checked, checkbox_checked_app, checkbox_unchecked, checkbox_unchecked_app, checkmark, checkmarkAppIcon, checkmarkFilledAppIcon, checkmarkIcon, checkmark_app, checkmark_filled_app, chevronDownIcon, chevronLeftAlignedappIcon, chevronLeftIcon, chevronRightAlignedappIcon, chevronRightIcon, chevronSmallLeftAlignedappIcon, chevronSmallRightAlignedappIcon, chevronUpIcon, chevron_down, chevron_left, chevron_left_alignedapp, chevron_right, chevron_right_alignedapp, chevron_small_left_alignedapp, chevron_small_right_alignedapp, chevron_up, circleFilledIcon, circleHalfFilledIcon, circleQuarterFilledIcon, circleThreeQuartersFilledIcon, city, cityIcon, clearAppIcon, clearRightAlignedappIcon, clear_app, clear_right_alignedapp, clock, clockIcon, close, closeAppIcon, closeCircleIcon, closeIcon, closeLeftAlignedappIcon, closeRightAlignedappIcon, close_app, close_circle, close_left_alignedapp, close_right_alignedapp, clrIconSVG, coffeeFilledAppIcon, coffee_filled_app, compassAppIcon, compass_app, completedByDateIcon, configuratorCommercialIcon, configuratorPrivateIcon, configurator_commercial, configurator_private, construction, constructionIcon, consumptionFuelFilledAppIcon, consumptionIcon, consumption_fuel, consumption_fuel_filled_app, contact, contactDealerFilledAppIcon, contactDealerIcon, contactIcon, contact_dealer, contact_dealer_filled_app, countryRoadIcon, country_road, craft, craftIcon, cupraBrandIcon, customerIcon, customerVipIcon, customerWaitingIcon, customersCenterIcon, customers_center, dataCopyAppIcon, dataExpiredIcon, dataFilledIcon, dataInputIcon, dataPlugAppIcon, dataSearchIcon, dataTimeExtensionIcon, data_copy_app, data_expired, data_filled, data_input, data_plug_app, data_search, data_time_extension, defaultSummaryAreaCollapsedKey, defogDefrostAutoAppIcon, defogDefrostIcon, defog_defrost, defog_defrost_auto_app, deliveryDateIcon, destinationAppIcon, destination_app, dieselIcon, direction, directionIcon, dischargingAppIcon, discharging_app, discountAppIcon, discount_app, discoveryAppIcon, discovery_app, dollarBillForwardIcon, dollarBillPartialIcon, download, downloadCustomIcon, dragIndicatorIcon, drag_indicator, driversAssistanceIcon, drivers_assistance, dropFilledAppIcon, drop_filled_app, dwaBrandIcon, eco, ecoIcon, edit, editIcon, editSmallRightAlignedAppIcon, edit_small_right_aligned_app, efficiency, efficiencyIcon, electricCarsIcon, electricCarsServiceIcon, electric_cars, electric_cars_service, electricity, electricityFilledAppIcon, electricityIcon, electricity_filled_app, emergency, emergencyIcon, emission, emissionIcon, energyIcon, engine, engineIcon, entertainment, entertainmentIcon, escapeHtml, escapeRegex, exportAppIcon, export_app, expressServiceIcon, express_service, exterior, exterior360Icon, exteriorIcon, exterior_360, externalPartForwardIcon, faq, faqIcon, fastForwardIcon, fast_forward, fax, faxIcon, filter, filterIcon, findACarIcon, findADealerIcon, find_a_car, find_a_dealer, firstRegistrationDateIcon, fleetServiceCommercialIcon, fleetServicePrivateIcon, fleet_service_commercial, fleet_service_private, folderFilledAppIcon, folder_filled_app, foodFilledAppIcon, food_filled_app, formatNumber, fullscreenEnterIcon, fullscreenExitIcon, fullscreen_enter, fullscreen_exit, gallery, galleryIcon, garageAppIcon, garage_app, gasAppIcon, gasCarsServiceIcon, gasIcon, gas_app, glassDamageAppIcon, glass_damage_app, gte, gteIcon, heart, heartFilledAppIcon, heartIcon, heart_filled_app, heightAppIcon, height_app, highwayRoadIcon, highway_road, history, historyIcon, homeAppIcon, homeEnergyAppIcon, homeFilledAppIcon, home_app, home_energy_app, home_filled_app, hornAppIcon, hornFilledAppIcon, horn_app, horn_filled_app, hybrid, hybridIcon, immediateChargingAppIcon, immediate_charging_app, info, infoFilledIcon, infoIcon, info_filled, inputHideIcon, inputShowIcon, input_hide, input_show, interior, interior360Icon, interiorIcon, interior_360, internalPartForwardIcon, internet, internetIcon, invitationAppIcon, invitation_app, invoiceIcon, invoiceReadyIcon, invoiceRecipientIcon, itemsForwardIcon, itemsRecieveIcon, jobportal, jobportalIcon, keyAppIcon, keyCardAppIcon, keyDigitalAppIcon, key_app, key_card_app, key_digital_app, keyboardAppIcon, keyboard_app, layerCollapseAppIcon, layerExpandAppIcon, layer_collapse_app, layer_expand_app, layersAppIcon, layers_app, legalTermsAndConditionsAppIcon, legal_terms_and_conditions_app, licencePlateAppIcon, licence_plate_app, lightAssistappIcon, light_assistapp, lightingAppIcon, lighting_app, linkExternAppIcon, link_extern_app, list, listIcon, loadingVolumeIcon, loading_volume, localBusinessIcon, local_business, locate, locateIcon, lock, lockIcon, lockOpenIcon, lock_open, login, loginIcon, logistic, logisticIcon, logoCommissionModuleFavIcon, logoCommissionModuleIcon, logoCommissionModuleNegativeFavIcon, logoCommissionModuleNegativeIcon, logoCostApprovalFavIcon, logoCostApprovalIcon, logoCostApprovalNegativeFavIcon, logoCostApprovalNegativeIcon, logoCrossControllingFavIcon, logoCrossControllingIcon, logoCrossControllingNegativeFavIcon, logoCrossControllingNegativeIcon, logoDigitalServiceReceptionFavIcon, logoDigitalServiceReceptionIcon, logoDigitalServiceReceptionNegativeFavIcon, logoDigitalServiceReceptionNegativeIcon, logoDocFlowFavIcon, logoDocFlowIcon, logoDocFlowNegativeFavIcon, logoDocFlowNegativeIcon, logoDocScanFavIcon, logoDocScanIcon, logoDocScanNegativeFavIcon, logoDocScanNegativeIcon, logoDocStoreFavIcon, logoDocStoreIcon, logoDocStoreNegativeFavIcon, logoDocStoreNegativeIcon, logoEBillingFavIcon, logoEBillingIcon, logoEBillingNegativeFavIcon, logoEBillingNegativeIcon, logoEPaymentFavIcon, logoEPaymentIcon, logoEPaymentNegativeFavIcon, logoEPaymentNegativeIcon, logoMobilityPlannerFavIcon, logoMobilityPlannerIcon, logoMobilityPlannerNegativeFavIcon, logoMobilityPlannerNegativeIcon, logoPartsMobileFavIcon, logoPartsMobileIcon, logoPartsMobileNegativeFavIcon, logoPartsMobileNegativeIcon, logoSBOFavIcon, logoSBOIcon, logoSBONegativeFavIcon, logoSBONegativeIcon, logoServiceCubeFavIcon, logoServiceCubeIcon, logoServiceCubeNegativeFavIcon, logoServiceCubeNegativeIcon, logoWCPFavIcon, logoWCPIcon, logoWCPNegativeFavIcon, logoWCPNegativeIcon, logoWorkshopOrderTrackerFavIcon, logoWorkshopOrderTrackerIcon, logoWorkshopOrderTrackerNegativeFavIcon, logoWorkshopOrderTrackerNegativeIcon, logout, logoutIcon, magnifier, magnifierIcon, magnifierMinusIcon, magnifierPlusIcon, magnifier_minus, magnifier_plus, mail, mailIcon, mailResendAppIcon, mail_resend_app, manual, manualIcon, map, mapIcon, mapToInternalTree, mechanicIcon, media, mediaIcon, menu, menuAppAppIcon, menuIcon, menu_app_app, microphoneAppIcon, microphone_app, mobile, mobileIcon, moreAppIcon, moreAppbarAppIcon, more_app, more_appbar_app, mot, motIcon, motability, motabilityIcon, navigate, navigateFilledAppIcon, navigateIcon, navigate_filled_app, newCarCommercialIcon, newCarPrivateFilledAppIcon, newCarPrivateIcon, newCarUtilityVehicleIcon, new_car_commercial, new_car_private, new_car_private_filled_app, nightServiceIcon, night_service, notification, notificationFilledIcon, notificationIcon, notification_filled, number0Icon, number10Icon, number11Icon, number12Icon, number13Icon, number14Icon, number15Icon, number16Icon, number17Icon, number18Icon, number19Icon, number1Icon, number20Icon, number2Icon, number3Icon, number4Icon, number5Icon, number6Icon, number7Icon, number8Icon, number9Icon, offers, offersFilledAppIcon, offersIcon, offers_filled_app, officeAppIcon, officeFilledAppIcon, office_app, office_filled_app, oilLevelIcon, oilLevelWarningIcon, oilTemperatureAppIcon, oil_level, oil_level_warning, oil_temperature_app, onCallDutyIcon, on_call_duty, openSatIcon, open_sat, orderIcon, orderStatusIcon, paintMaterialForwardIcon, paintMaterialIcon, paintShopIcon, paint_shop, paragraphAppIcon, paragraph_app, parkHeaterAppIcon, park_heater_app, parking, parkingFilledAppIcon, parkingGarageAppIcon, parkingIcon, parkingLocationIcon, parkingRouteAppIcon, parkingValetAppIcon, parking_filled_app, parking_garage_app, parking_route_app, parking_valet_app, partAvailabilityInfoIcon, partAvailabilityNoIcon, partAvailabilityUnknownIcon, partAvailabilityWarningIcon, partAvailabilityYesIcon, partIdenticalIcon, partIdenticalPredecessorIcon, partIdenticalSuccessorIcon, partIdenticalSuccpredecessorIcon, partPredecessorIcon, partSuccessorIcon, partSuccessorPredecessorIcon, partsChangelocationIcon, partsForwardIcon, partsIcon, partsInventoryIcon, partsNonStockForwardIcon, partsNonStockIcon, partsPickingIcon, partsPickingPlusIcon, partsReceivingIcon, pause, pauseIcon, payload, payloadIcon, paymentAppIcon, paymentCashAppIcon, paymentChargingCardAppIcon, paymentCreditcardAppIcon, paymentMachineAppIcon, payment_app, payment_cash_app, payment_charging_card_app, payment_creditcard_app, payment_machine_app, performance, performanceIcon, petrol, petrolIcon, phone, phoneIcon, pin, pinFilledAppIcon, pinGenericFilledAppIcon, pinIcon, pin_filled_app, pin_generic_filled_app, play, playIcon, plugCcsAppIcon, plugChademoAppIcon, plugChargeAppIcon, plugGenericAppIcon, plugSchukoAppIcon, plugType1AppIcon, plugType2AppIcon, plug_ccs_app, plug_chademo_app, plug_charge_app, plug_generic_app, plug_schuko_app, plug_type1_app, plug_type2_app, plusServiceIcon, porscheBrandIcon, power, powerIcon, powerTrainIcon, powertrain, preHeaterAppIcon, pre_heater_app, preciseLaneNavigationAppIcon, precise_lane_navigation_app, presentAppIcon, present_app, priceTypeSwitchIcon, printer, printerIcon, privacyAppIcon, privacy_app, profile, profileIcon, profileRegisterAppIcon, profileVerifiedIcon, profile_register_app, profile_verified, publicServiceIcon, publicTransportAppIcon, public_service, public_transport_app, qualifiedWorkshopIcon, qualified_workshop, questionnaireAppIcon, questionnaire_app, radio, radioButtonInselectedIcon, radioButtonSelectedForDefIcon, radioButtonSelectedIcon, radioIcon, radio_button_inselected, radio_button_selected, radio_button_selected_for_development, range, rangeIcon, reload, reloadIcon, remove, removeIcon, repeat, repeatIcon, repeatRepairIcon, replacementVehicleIcon, returnDateIcon, rewind, rewindIcon, roadsideAssistanceIcon, roadside_assistance, route, routeArrowAppIcon, routeIcon, route_arrow_app, routesHistoryAppIcon, routes_history_app, rss, rssIcon, safety, safetyIcon, save, saveAppIcon, saveIcon, save_app, seat, seatAirIcon, seatBrandIcon, seatIcon, seat_air, secretTipAppIcon, secretTipFilledAppIcon, secret_tip_app, secret_tip_filled_app, selected, selectedIcon, selectedPartnerNetworkAppIcon, selected_partner_network_app, sendToCarAppIcon, send_to_car_app, service, serviceAdvisorIcon, serviceBellIcon, serviceFilledAppIcon, serviceIcon, service_bell, service_filled_app, settings, settingsIcon, shareAndroidIcon, shareIosIcon, share_android, share_ios, shoppingCartFilledAppIcon, shoppingCartIcon, shopping_cart, shopping_cart_filled_app, shuffle, shuffleIcon, size, sizeIcon, skillAppIcon, skill_app, skipBackwardIcon, skipForwardIcon, skip_backward, skip_forward, skodaBrandIcon, softwareDownloadAppIcon, software_download_app, sortingAppIcon, sorting_app, sound, soundIcon, standardEquipmentIcon, standard_equipment, starFilledIcon, starOutlineIcon, star_filled, star_outline, statisticAppIcon, statistic_app, stockLocatorCommercialIcon, stockLocatorPrivateIcon, stock_locator_commercial, stock_locator_private, stop, stopIcon, strip, switchPositionAppIcon, switch_position_app, syncAppIcon, sync_app, taskAndAppointmentIcon, taxiDealerIcon, taxi_dealer, technicalSpecificationIcon, technical_specification, temperatureAppIcon, temperature_app, testDriveIcon, test_drive, textForwardIcon, thumbsdownAppIcon, thumbsdownFilledAppIcon, thumbsdown_app, thumbsdown_filled_app, thumbsupAppIcon, thumbsupFilledAppIcon, thumbsup_app, thumbsup_filled_app, timeClimatisationAppIcon, timePreferredAppIcon, time_climatisation_app, time_preferred_app, timer, timerIcon, topcardIcon, touaregServiceIcon, transcriptDownloadIcon, transcript_download, transmissionAutomaticIcon, transmissionManualIcon, transmission_automatic, transmission_manual, tripAppIcon, tripLongAppIcon, tripPartedAppIcon, tripShortAppIcon, trip_app, trip_long_app, trip_parted_app, trip_short_app, turnSignalsIcon, turn_signals, unselected, unselectedIcon, updateRefreshAppIcon, update_refresh_app, upload, uploadAppIcon, uploadCustomIcon, upload_app, usedCarCommercialIcon, usedCarPrivateIcon, used_car_commercial, used_car_private, vehicleAmarokIcon, vehicleCaddyIcon, vehicleConversionIcon, vehicleCrafterIcon, vehicleHightIcon, vehicleIdBuzzIcon, vehicleMultivanIcon, vehicleTransporterIcon, vehicle_amarok, vehicle_caddy, vehicle_crafter, vehicle_hight, vehicle_idbuzz, vehicle_multivan, vehicle_transporter, videoChatIcon, video_chat, view360Icon, view_360, vinIcon, virtualRealityIcon, virtual_reality, voiceMessageAppIcon, voice_message_app, volkswagenAppIcon, volkswagenIcon, volkswagen_app, volumeMaximumIcon, volumeMediumIcon, volumeMuteIcon, volume_maximum, volume_medium, volume_mute, vsfSearch48Icon, vsfSearchIcon, vwBrandIcon, vwConnectLicenseAppIcon, vw_connect_license_app, vwnBrandIcon, walkingAppIcon, walkingFilledAppIcon, walking_app, walking_filled_app, wallbox, wallboxIcon, wcAppIcon, wc_app, wcpIcon, weAssistAppIcon, weChargeAppIcon, weDeliverAppIcon, weExperienceAppIcon, weParkAppIcon, weUpgradeAppIcon, we_assist_app, we_charge_app, we_deliver_app, we_experience_app, we_park_app, we_upgrade_app, weatherSunAppIcon, weather_sun_app, wheelToWheelIcon, wheel_to_wheel, windscreenWashIcon, windscreen_wash, wlanHotspotIcon, wlan_hotspot, wrenchForwardIcon };
19478
+ export { ACShape, AcceptanceDateShape, AcceptedBrands, AccessoryPartsShape, AreaChartComponent, AudiBrandShape, AwardWinnerPremiumShape, BIG_ENDIAN, BarChartComponent, BlocksGroupForwardShape, BundleForwardShape, BusinessCustomersCommercialShape, BusinessCustomersPrivateShape, BusinessPartnerWithCar, CLR_BLANK_OPTION, CONTENT_PROVIDER, CalculatorForwardShape, CaliforniaServiceShape, CampaignOutdatedShape, CampaignShape, CarOffSite, CarOnSite, CircleFilled, CircleHalfFilled, CircleQuarterFilled, CircleThreeQuartersFilled, ClrActionPanel, ClrActionPanelContainer, ClrActionPanelContainerContent, ClrActionPanelContainerFooter, ClrActionPanelModule, ClrActiveNotification, ClrAddonsLabel, ClrAddonsModule, ClrAutocompleteOff, ClrAutocompleteOffModule, ClrBackButton, ClrBackButtonModule, ClrBrandAvatar, ClrBrandAvatarModule, ClrBreadcrumb, ClrBreadcrumbModule, ClrBreadcrumbService, ClrChartsModule, ClrCollapseExpandSection, ClrCollapseExpandSectionModule, ClrContentPanel, ClrContentPanelContainer, ClrContentPanelContainerContent, ClrContentPanelContainerFooter, ClrContentPanelModule, ClrContentRef, ClrControlEnterSubmitDirective, ClrCopyToClipboard, ClrDataListPredefinedValidatorDirective, ClrDataListValidatorModule, ClrDataListValidators, ClrDatagridColumnReorderModule, ClrDatagridStatePersistenceModule, ClrDateFilterComponent, ClrDateFilterModule, ClrDateTimeContainer, ClrDateTimeModule, ClrDaterangeMaxValidator, ClrDaterangeMinValidator, ClrDaterangeOrderValidator, ClrDaterangeRequiredValidator, ClrDaterangepickerContainerComponent, ClrDaterangepickerDirective, ClrDaterangepickerModule, ClrDotPager, ClrDotPagerModule, ClrDropdownOverflowDirective, ClrDropdownOverflowModule, ClrEnumFilterComponent, ClrEnumFilterModule, ClrExportDatagridButtonModule, ClrFlowBar, ClrFlowBarModule, ClrFocusFirstInvalidFieldDirective, ClrFormModule, ClrGenericQuickList, ClrGenericQuickListModule, ClrHistory, ClrHistoryModule, ClrHistoryPinned, ClrHistoryService, ClrIconAvatar, ClrIconAvatarModule, ClrIfDaterangeErrorDirective, ClrIfWarning, ClrIfWarningModule, ClrLetterAvatar, ClrLetterAvatarModule, ClrLocationBarModule, ClrMainNavGroup, ClrMainNavGroupItem, ClrMainNavGroupModule, ClrMaxNumeric, ClrMinNumeric, ClrMultilingualInput, ClrMultilingualInputValidators, ClrMultilingualModule, ClrMultilingualSelector, ClrMultilingualTextarea, ClrNotification, ClrNotificationModule, ClrNotificationRef, ClrNotificationService, ClrNumericField, ClrNumericFieldModule, ClrNumericFieldValidators, ClrPagedSearchResultList, ClrPagedSearchResultListModule, ClrPager, ClrPagerModule, ClrProgressSpinnerComponent, ClrProgressSpinnerModule, ClrQuickList, ClrQuickListModule, ClrReadonlyDirective, ClrReadonlyDirectiveModule, ClrRequiredAllMultilang, ClrRequiredOneMultilang, ClrSearchField, ClrSearchFieldModule, ClrSignpostAddonComponent, ClrSignpostAddonModule, ClrSummaryArea, ClrSummaryAreaStateService, ClrSummaryAreaToggle, ClrSummaryItem, ClrSummaryItemValue, ClrTimeInput, ClrTreetable, ClrTreetableActionOverflow, ClrTreetableCell, ClrTreetableColumn, ClrTreetableFilter, ClrTreetableModule, ClrTreetablePlaceholder, ClrTreetableRow, ClrTreetableSortOrder, ClrTreetableStringFilter, ClrTreetableTreeNode, ClrViewEditSection, ClrViewEditSectionModule, ColumnHiddenStatePersistenceDirective, ComboChartComponent, CompletedByDateShape, CupraBrandShape, Customer, CustomerVip, CustomerVipCollection, CustomerWaiting, CustomerWaitingCollection, DATE, DELIMITER_REGEX, DWABrandShape, DatagridColumnReorderDirective, DatagridFieldDirective, DayModel, DeliveryDate, DieselShape, DollarBillForwardShape, DollarBillPartialShape, DynamicCellContentComponent, EnergyShape, ExportDatagridButtonComponent, ExportDatagridService, ExportTypeEnum, ExternalPartForwardShape, FirstRegistrationDate, FunnelChartComponent, GasCarsServiceShape, GasShape, HISTORY_NOTIFICATION_URL_PROVIDER, HISTORY_PROVIDER, HISTORY_TOKEN, HistoryProvider, InternalPartForwardShape, InvoiceReadyShape, InvoiceRecipient, InvoiceShape, ItemsForwardShape, ItemsReceiveShape, LITTLE_ENDIAN, LITTLE_ENDIAN_REGEX, LineChartComponent, LocationBarComponent, LocationBarContentProvider, LocationBarNode, LogoCommissionModule, LogoCommissionModuleFavIcon, LogoCommissionModuleNegative, LogoCommissionModuleNegativeFavIcon, LogoCostApproval, LogoCostApprovalFavIcon, LogoCostApprovalNegative, LogoCostApprovalNegativeFavIcon, LogoCostControlling, LogoCostControllingFavIcon, LogoCostControllingNegative, LogoCostControllingNegativeFavIcon, LogoDigitalServiceReception, LogoDigitalServiceReceptionFavIcon, LogoDigitalServiceReceptionNegative, LogoDigitalServiceReceptionNegativeFavIcon, LogoDocFlow, LogoDocFlowFavIcon, LogoDocFlowNegative, LogoDocFlowNegativeFavIcon, LogoDocScan, LogoDocScanFavIcon, LogoDocScanNegative, LogoDocScanNegativeFavIcon, LogoDocStore, LogoDocStoreFavIcon, LogoDocStoreNegative, LogoDocStoreNegativeFavIcon, LogoEBilling, LogoEBillingFavIcon, LogoEBillingNegative, LogoEBillingNegativeFavIcon, LogoEPayment, LogoEPaymentFavIcon, LogoEPaymentNegative, LogoEPaymentNegativeFavIcon, LogoMobilityPlanner, LogoMobilityPlannerFavIcon, LogoMobilityPlannerNegative, LogoMobilityPlannerNegativeFavIcon, LogoPartsMobile, LogoPartsMobileFavIcon, LogoPartsMobileNegative, LogoPartsMobileNegativeFavIcon, LogoSBO, LogoSBOFavIcon, LogoSBONegative, LogoSBONegativeFavIcon, LogoServiceCube, LogoServiceCubeFavIcon, LogoServiceCubeNegative, LogoServiceCubeNegativeFavIcon, LogoWCP, LogoWCPFavIcon, LogoWCPNegative, LogoWCPNegativeFavIcon, LogoWorkshopOrderTracker, LogoWorkshopOrderTrackerFavIcon, LogoWorkshopOrderTrackerNegative, LogoWorkshopOrderTrackerNegativeFavIcon, MIDDLE_ENDIAN, MIDDLE_ENDIAN_REGEX, MONTH, Mechanic, NewCarUtilityVehicleShape, NodeId, Number0, Number1, Number10, Number11, Number12, Number13, Number14, Number15, Number16, Number17, Number18, Number19, Number2, Number20, Number3, Number4, Number5, Number6, Number7, Number8, Number9, OrderShape, OrderStatusShape, PaintMaterialForwardShape, PaintMaterialShape, ParkingLocation, PartAvailabilityInfoShape, PartAvailabilityNoShape, PartAvailabilityUnknownShape, PartAvailabilityWarningShape, PartAvailabilityYesShape, PartIdenticalPredecessorShape, PartIdenticalShape, PartIdenticalSuccessorShape, PartIdenticalSuccpredecessorShape, PartNonStockForwardShape, PartPredecessorShape, PartSuccessorPredecessorShape, PartSuccessorShape, PartsChangelocation, PartsForwardShape, PartsInventory, PartsNonStockShape, PartsPicking, PartsPickingPlus, PartsReceiving, PartsShape, PieChartComponent, PlusServiceShape, PopoverPositions, PorscheBrandShape, PriceTypeSwitchShape, RepeatRepairCollection, RepeatRepairShape, ReplacementVehicleCollection, ReplacementVehicleShape, ReturnDateShape, SEPARATOR_TEXT_DEFAULT, SeatBrandShape, ServiceAdvisor, SkodaBrandShape, Sort, StatePersistenceKeyDirective, TRANSLATIONS, TaskAndAppointment, TextForward, TimeModel, TopcardShape, TouaregServiceShape, TreetableCellRenderer, TreetableDataStateService, TreetableHeaderRenderer, TreetableItemsDirective, TreetableMainRenderer, TreetableRowRenderer, USER_INPUT_REGEX, VWBrandShape, VWNBrandShape, VWShape, VehicleConversionShape, VinShape, VsfSearchShape, VsfSearchShape48, WCPShape, WrenchForward, YEAR, acceleration, accelerationIcon, acceptanceDateIcon, accessories, accessoriesIcon, accessoryPartsIcon, adblueAppIcon, adblue_app, add, addIcon, airConditionerIcon, air_conditioning, alert, alertFilledAppIcon, alertIcon, alertNotificationFilledAppIcon, alert_filled_app, alert_notification_filled_app, allIcons, ambientLightAppIcon, ambient_light_app, amplifier, amplifierIcon, appConnectAppIcon, app_connect_app, archive, archiveIcon, arrowDownIcon, arrowLeftAlignedAppIcon, arrowLeftIcon, arrowRightIcon, arrowSliderAppIcon, arrowUpIcon, arrow_down, arrow_left, arrow_left_aligned_app, arrow_right, arrow_slider_app, arrow_up, attachment, attachmentIcon, audiBrandIcon, authentPlugChargeAppIcon, authentQrAppIcon, authentRfidAppIcon, authentTouchidAppIcon, authent_plug_charge_app, authent_qr_app, authent_rfid_app, authent_touch_id_app, automaticTempAppIcon, automatic_temp_app, awardWinnerPremiumIcon, award_winner_premium, back, backIcon, battery, batteryIcon, batterySocChargingAppIcon, batterySocDepartureAppIcon, batterySocDestinationAppIcon, battery_soc_charging_app, battery_soc_departure_app, battery_soc_destination_app, bin, binIcon, blocksGroupForwardIcon, bluetooth, bluetoothIcon, bookmark, bookmarkFilledIcon, bookmarkIcon, bookmark_filled, brakeAppIcon, brake_app, brochure, brochureIcon, bulletpointAppIcon, bulletpoint_app, bundleForwardIcon, businessCustomersCommercialIcon, businessCustomersPrivateIcon, businessPartnerWithCarIcon, business_customers_commercial, business_customers_private, calc, calcIcon, calculatorForwardIcon, calendar, calendarCustomIcon, californiaServiceIcon, californiaSpecialistIcon, california_specialist, cameraScanIcon, camera_scan, campaignIcon, campaignOutdatedIcon, carDocumentsIcon, carErrorAppIcon, carInsuranceIcon, carOffSite, carOnSite, carPickupServiceIcon, carPlusIcon, carSettingsIcon, carVerifiedAppIcon, carWashIcon, carWheelAppIcon, car_documents, car_error_app, car_insurance, car_pickup_service, car_plus, car_settings, car_verified_app, car_wheel_app, carwash, certifiedRepairIcon, certifiedRetailerIcon, certified_repair, certified_retailer, challengeAppIcon, challenge_app, charging, chargingIcon, chargingPduAppIcon, chargingStationIcon, chargingTarifOverviewAppIcon, charging_pdu_app, charging_station, charging_tarif_overview_app, chat, chatAppIcon, chatIcon, chat_app, checkboxCheckedAppIcon, checkboxCheckedIcon, checkboxUncheckedAppIcon, checkboxUncheckedIcon, checkbox_checked, checkbox_checked_app, checkbox_unchecked, checkbox_unchecked_app, checkmark, checkmarkAppIcon, checkmarkFilledAppIcon, checkmarkIcon, checkmark_app, checkmark_filled_app, chevronDownIcon, chevronLeftAlignedappIcon, chevronLeftIcon, chevronRightAlignedappIcon, chevronRightIcon, chevronSmallLeftAlignedappIcon, chevronSmallRightAlignedappIcon, chevronUpIcon, chevron_down, chevron_left, chevron_left_alignedapp, chevron_right, chevron_right_alignedapp, chevron_small_left_alignedapp, chevron_small_right_alignedapp, chevron_up, circleFilledIcon, circleHalfFilledIcon, circleQuarterFilledIcon, circleThreeQuartersFilledIcon, city, cityIcon, clearAppIcon, clearRightAlignedappIcon, clear_app, clear_right_alignedapp, clock, clockIcon, close, closeAppIcon, closeCircleIcon, closeIcon, closeLeftAlignedappIcon, closeRightAlignedappIcon, close_app, close_circle, close_left_alignedapp, close_right_alignedapp, clrIconSVG, coffeeFilledAppIcon, coffee_filled_app, compassAppIcon, compass_app, completedByDateIcon, configuratorCommercialIcon, configuratorPrivateIcon, configurator_commercial, configurator_private, construction, constructionIcon, consumptionFuelFilledAppIcon, consumptionIcon, consumption_fuel, consumption_fuel_filled_app, contact, contactDealerFilledAppIcon, contactDealerIcon, contactIcon, contact_dealer, contact_dealer_filled_app, countryRoadIcon, country_road, craft, craftIcon, cupraBrandIcon, customerIcon, customerVipIcon, customerWaitingIcon, customersCenterIcon, customers_center, dataCopyAppIcon, dataExpiredIcon, dataFilledIcon, dataInputIcon, dataPlugAppIcon, dataSearchIcon, dataTimeExtensionIcon, data_copy_app, data_expired, data_filled, data_input, data_plug_app, data_search, data_time_extension, defaultSummaryAreaCollapsedKey, defogDefrostAutoAppIcon, defogDefrostIcon, defog_defrost, defog_defrost_auto_app, deliveryDateIcon, destinationAppIcon, destination_app, dieselIcon, direction, directionIcon, dischargingAppIcon, discharging_app, discountAppIcon, discount_app, discoveryAppIcon, discovery_app, dollarBillForwardIcon, dollarBillPartialIcon, download, downloadCustomIcon, dragIndicatorIcon, drag_indicator, driversAssistanceIcon, drivers_assistance, dropFilledAppIcon, drop_filled_app, dwaBrandIcon, eco, ecoIcon, edit, editIcon, editSmallRightAlignedAppIcon, edit_small_right_aligned_app, efficiency, efficiencyIcon, electricCarsIcon, electricCarsServiceIcon, electric_cars, electric_cars_service, electricity, electricityFilledAppIcon, electricityIcon, electricity_filled_app, emergency, emergencyIcon, emission, emissionIcon, energyIcon, engine, engineIcon, entertainment, entertainmentIcon, escapeHtml, escapeRegex, exportAppIcon, export_app, expressServiceIcon, express_service, exterior, exterior360Icon, exteriorIcon, exterior_360, externalPartForwardIcon, faq, faqIcon, fastForwardIcon, fast_forward, fax, faxIcon, filter, filterIcon, findACarIcon, findADealerIcon, find_a_car, find_a_dealer, firstRegistrationDateIcon, fleetServiceCommercialIcon, fleetServicePrivateIcon, fleet_service_commercial, fleet_service_private, folderFilledAppIcon, folder_filled_app, foodFilledAppIcon, food_filled_app, formatNumber, fullscreenEnterIcon, fullscreenExitIcon, fullscreen_enter, fullscreen_exit, gallery, galleryIcon, garageAppIcon, garage_app, gasAppIcon, gasCarsServiceIcon, gasIcon, gas_app, glassDamageAppIcon, glass_damage_app, gte, gteIcon, heart, heartFilledAppIcon, heartIcon, heart_filled_app, heightAppIcon, height_app, highwayRoadIcon, highway_road, history, historyIcon, homeAppIcon, homeEnergyAppIcon, homeFilledAppIcon, home_app, home_energy_app, home_filled_app, hornAppIcon, hornFilledAppIcon, horn_app, horn_filled_app, hybrid, hybridIcon, immediateChargingAppIcon, immediate_charging_app, info, infoFilledIcon, infoIcon, info_filled, inputHideIcon, inputShowIcon, input_hide, input_show, interior, interior360Icon, interiorIcon, interior_360, internalPartForwardIcon, internet, internetIcon, invitationAppIcon, invitation_app, invoiceIcon, invoiceReadyIcon, invoiceRecipientIcon, itemsForwardIcon, itemsRecieveIcon, jobportal, jobportalIcon, keyAppIcon, keyCardAppIcon, keyDigitalAppIcon, key_app, key_card_app, key_digital_app, keyboardAppIcon, keyboard_app, layerCollapseAppIcon, layerExpandAppIcon, layer_collapse_app, layer_expand_app, layersAppIcon, layers_app, legalTermsAndConditionsAppIcon, legal_terms_and_conditions_app, licencePlateAppIcon, licence_plate_app, lightAssistappIcon, light_assistapp, lightingAppIcon, lighting_app, linkExternAppIcon, link_extern_app, list, listIcon, loadingVolumeIcon, loading_volume, localBusinessIcon, local_business, locate, locateIcon, lock, lockIcon, lockOpenIcon, lock_open, login, loginIcon, logistic, logisticIcon, logoCommissionModuleFavIcon, logoCommissionModuleIcon, logoCommissionModuleNegativeFavIcon, logoCommissionModuleNegativeIcon, logoCostApprovalFavIcon, logoCostApprovalIcon, logoCostApprovalNegativeFavIcon, logoCostApprovalNegativeIcon, logoCrossControllingFavIcon, logoCrossControllingIcon, logoCrossControllingNegativeFavIcon, logoCrossControllingNegativeIcon, logoDigitalServiceReceptionFavIcon, logoDigitalServiceReceptionIcon, logoDigitalServiceReceptionNegativeFavIcon, logoDigitalServiceReceptionNegativeIcon, logoDocFlowFavIcon, logoDocFlowIcon, logoDocFlowNegativeFavIcon, logoDocFlowNegativeIcon, logoDocScanFavIcon, logoDocScanIcon, logoDocScanNegativeFavIcon, logoDocScanNegativeIcon, logoDocStoreFavIcon, logoDocStoreIcon, logoDocStoreNegativeFavIcon, logoDocStoreNegativeIcon, logoEBillingFavIcon, logoEBillingIcon, logoEBillingNegativeFavIcon, logoEBillingNegativeIcon, logoEPaymentFavIcon, logoEPaymentIcon, logoEPaymentNegativeFavIcon, logoEPaymentNegativeIcon, logoMobilityPlannerFavIcon, logoMobilityPlannerIcon, logoMobilityPlannerNegativeFavIcon, logoMobilityPlannerNegativeIcon, logoPartsMobileFavIcon, logoPartsMobileIcon, logoPartsMobileNegativeFavIcon, logoPartsMobileNegativeIcon, logoSBOFavIcon, logoSBOIcon, logoSBONegativeFavIcon, logoSBONegativeIcon, logoServiceCubeFavIcon, logoServiceCubeIcon, logoServiceCubeNegativeFavIcon, logoServiceCubeNegativeIcon, logoWCPFavIcon, logoWCPIcon, logoWCPNegativeFavIcon, logoWCPNegativeIcon, logoWorkshopOrderTrackerFavIcon, logoWorkshopOrderTrackerIcon, logoWorkshopOrderTrackerNegativeFavIcon, logoWorkshopOrderTrackerNegativeIcon, logout, logoutIcon, magnifier, magnifierIcon, magnifierMinusIcon, magnifierPlusIcon, magnifier_minus, magnifier_plus, mail, mailIcon, mailResendAppIcon, mail_resend_app, manual, manualIcon, map, mapIcon, mapToInternalTree, mechanicIcon, media, mediaIcon, menu, menuAppAppIcon, menuIcon, menu_app_app, microphoneAppIcon, microphone_app, mobile, mobileIcon, moreAppIcon, moreAppbarAppIcon, more_app, more_appbar_app, mot, motIcon, motability, motabilityIcon, navigate, navigateFilledAppIcon, navigateIcon, navigate_filled_app, newCarCommercialIcon, newCarPrivateFilledAppIcon, newCarPrivateIcon, newCarUtilityVehicleIcon, new_car_commercial, new_car_private, new_car_private_filled_app, nightServiceIcon, night_service, notification, notificationFilledIcon, notificationIcon, notification_filled, number0Icon, number10Icon, number11Icon, number12Icon, number13Icon, number14Icon, number15Icon, number16Icon, number17Icon, number18Icon, number19Icon, number1Icon, number20Icon, number2Icon, number3Icon, number4Icon, number5Icon, number6Icon, number7Icon, number8Icon, number9Icon, offers, offersFilledAppIcon, offersIcon, offers_filled_app, officeAppIcon, officeFilledAppIcon, office_app, office_filled_app, oilLevelIcon, oilLevelWarningIcon, oilTemperatureAppIcon, oil_level, oil_level_warning, oil_temperature_app, onCallDutyIcon, on_call_duty, openSatIcon, open_sat, orderIcon, orderStatusIcon, paintMaterialForwardIcon, paintMaterialIcon, paintShopIcon, paint_shop, paragraphAppIcon, paragraph_app, parkHeaterAppIcon, park_heater_app, parking, parkingFilledAppIcon, parkingGarageAppIcon, parkingIcon, parkingLocationIcon, parkingRouteAppIcon, parkingValetAppIcon, parking_filled_app, parking_garage_app, parking_route_app, parking_valet_app, partAvailabilityInfoIcon, partAvailabilityNoIcon, partAvailabilityUnknownIcon, partAvailabilityWarningIcon, partAvailabilityYesIcon, partIdenticalIcon, partIdenticalPredecessorIcon, partIdenticalSuccessorIcon, partIdenticalSuccpredecessorIcon, partPredecessorIcon, partSuccessorIcon, partSuccessorPredecessorIcon, partsChangelocationIcon, partsForwardIcon, partsIcon, partsInventoryIcon, partsNonStockForwardIcon, partsNonStockIcon, partsPickingIcon, partsPickingPlusIcon, partsReceivingIcon, pause, pauseIcon, payload, payloadIcon, paymentAppIcon, paymentCashAppIcon, paymentChargingCardAppIcon, paymentCreditcardAppIcon, paymentMachineAppIcon, payment_app, payment_cash_app, payment_charging_card_app, payment_creditcard_app, payment_machine_app, performance, performanceIcon, petrol, petrolIcon, phone, phoneIcon, pin, pinFilledAppIcon, pinGenericFilledAppIcon, pinIcon, pin_filled_app, pin_generic_filled_app, play, playIcon, plugCcsAppIcon, plugChademoAppIcon, plugChargeAppIcon, plugGenericAppIcon, plugSchukoAppIcon, plugType1AppIcon, plugType2AppIcon, plug_ccs_app, plug_chademo_app, plug_charge_app, plug_generic_app, plug_schuko_app, plug_type1_app, plug_type2_app, plusServiceIcon, porscheBrandIcon, power, powerIcon, powerTrainIcon, powertrain, preHeaterAppIcon, pre_heater_app, preciseLaneNavigationAppIcon, precise_lane_navigation_app, presentAppIcon, present_app, priceTypeSwitchIcon, printer, printerIcon, privacyAppIcon, privacy_app, profile, profileIcon, profileRegisterAppIcon, profileVerifiedIcon, profile_register_app, profile_verified, publicServiceIcon, publicTransportAppIcon, public_service, public_transport_app, qualifiedWorkshopIcon, qualified_workshop, questionnaireAppIcon, questionnaire_app, radio, radioButtonInselectedIcon, radioButtonSelectedForDefIcon, radioButtonSelectedIcon, radioIcon, radio_button_inselected, radio_button_selected, radio_button_selected_for_development, range, rangeIcon, reload, reloadIcon, remove, removeIcon, repeat, repeatIcon, repeatRepairIcon, replacementVehicleIcon, returnDateIcon, rewind, rewindIcon, roadsideAssistanceIcon, roadside_assistance, route, routeArrowAppIcon, routeIcon, route_arrow_app, routesHistoryAppIcon, routes_history_app, rss, rssIcon, safety, safetyIcon, save, saveAppIcon, saveIcon, save_app, seat, seatAirIcon, seatBrandIcon, seatIcon, seat_air, secretTipAppIcon, secretTipFilledAppIcon, secret_tip_app, secret_tip_filled_app, selected, selectedIcon, selectedPartnerNetworkAppIcon, selected_partner_network_app, sendToCarAppIcon, send_to_car_app, service, serviceAdvisorIcon, serviceBellIcon, serviceFilledAppIcon, serviceIcon, service_bell, service_filled_app, settings, settingsIcon, shareAndroidIcon, shareIosIcon, share_android, share_ios, shoppingCartFilledAppIcon, shoppingCartIcon, shopping_cart, shopping_cart_filled_app, shuffle, shuffleIcon, size, sizeIcon, skillAppIcon, skill_app, skipBackwardIcon, skipForwardIcon, skip_backward, skip_forward, skodaBrandIcon, softwareDownloadAppIcon, software_download_app, sortingAppIcon, sorting_app, sound, soundIcon, standardEquipmentIcon, standard_equipment, starFilledIcon, starOutlineIcon, star_filled, star_outline, statisticAppIcon, statistic_app, stockLocatorCommercialIcon, stockLocatorPrivateIcon, stock_locator_commercial, stock_locator_private, stop, stopIcon, strip, switchPositionAppIcon, switch_position_app, syncAppIcon, sync_app, taskAndAppointmentIcon, taxiDealerIcon, taxi_dealer, technicalSpecificationIcon, technical_specification, temperatureAppIcon, temperature_app, testDriveIcon, test_drive, textForwardIcon, thumbsdownAppIcon, thumbsdownFilledAppIcon, thumbsdown_app, thumbsdown_filled_app, thumbsupAppIcon, thumbsupFilledAppIcon, thumbsup_app, thumbsup_filled_app, timeClimatisationAppIcon, timePreferredAppIcon, time_climatisation_app, time_preferred_app, timer, timerIcon, topcardIcon, touaregServiceIcon, transcriptDownloadIcon, transcript_download, transmissionAutomaticIcon, transmissionManualIcon, transmission_automatic, transmission_manual, tripAppIcon, tripLongAppIcon, tripPartedAppIcon, tripShortAppIcon, trip_app, trip_long_app, trip_parted_app, trip_short_app, turnSignalsIcon, turn_signals, unselected, unselectedIcon, updateRefreshAppIcon, update_refresh_app, upload, uploadAppIcon, uploadCustomIcon, upload_app, usedCarCommercialIcon, usedCarPrivateIcon, used_car_commercial, used_car_private, vehicleAmarokIcon, vehicleCaddyIcon, vehicleConversionIcon, vehicleCrafterIcon, vehicleHightIcon, vehicleIdBuzzIcon, vehicleMultivanIcon, vehicleTransporterIcon, vehicle_amarok, vehicle_caddy, vehicle_crafter, vehicle_hight, vehicle_idbuzz, vehicle_multivan, vehicle_transporter, videoChatIcon, video_chat, view360Icon, view_360, vinIcon, virtualRealityIcon, virtual_reality, voiceMessageAppIcon, voice_message_app, volkswagenAppIcon, volkswagenIcon, volkswagen_app, volumeMaximumIcon, volumeMediumIcon, volumeMuteIcon, volume_maximum, volume_medium, volume_mute, vsfSearch48Icon, vsfSearchIcon, vwBrandIcon, vwConnectLicenseAppIcon, vw_connect_license_app, vwnBrandIcon, walkingAppIcon, walkingFilledAppIcon, walking_app, walking_filled_app, wallbox, wallboxIcon, wcAppIcon, wc_app, wcpIcon, weAssistAppIcon, weChargeAppIcon, weDeliverAppIcon, weExperienceAppIcon, weParkAppIcon, weUpgradeAppIcon, we_assist_app, we_charge_app, we_deliver_app, we_experience_app, we_park_app, we_upgrade_app, weatherSunAppIcon, weather_sun_app, wheelToWheelIcon, wheel_to_wheel, windscreenWashIcon, windscreen_wash, wlanHotspotIcon, wlan_hotspot, wrenchForwardIcon };
16953
19479
  //# sourceMappingURL=clr-addons.mjs.map