@yuuvis/client-components 3.0.0-beta.21.1

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 (47) hide show
  1. package/README.md +3 -0
  2. package/autocomplete/README.md +3 -0
  3. package/common/README.md +3 -0
  4. package/datepicker/README.md +3 -0
  5. package/fesm2022/yuuvis-client-components-autocomplete.mjs +236 -0
  6. package/fesm2022/yuuvis-client-components-autocomplete.mjs.map +1 -0
  7. package/fesm2022/yuuvis-client-components-common.mjs +1724 -0
  8. package/fesm2022/yuuvis-client-components-common.mjs.map +1 -0
  9. package/fesm2022/yuuvis-client-components-datepicker.mjs +456 -0
  10. package/fesm2022/yuuvis-client-components-datepicker.mjs.map +1 -0
  11. package/fesm2022/yuuvis-client-components-list.mjs +666 -0
  12. package/fesm2022/yuuvis-client-components-list.mjs.map +1 -0
  13. package/fesm2022/yuuvis-client-components-master-details.mjs +136 -0
  14. package/fesm2022/yuuvis-client-components-master-details.mjs.map +1 -0
  15. package/fesm2022/yuuvis-client-components-overflow-hidden.mjs +109 -0
  16. package/fesm2022/yuuvis-client-components-overflow-hidden.mjs.map +1 -0
  17. package/fesm2022/yuuvis-client-components-overflow-menu.mjs +171 -0
  18. package/fesm2022/yuuvis-client-components-overflow-menu.mjs.map +1 -0
  19. package/fesm2022/yuuvis-client-components-popout.mjs +240 -0
  20. package/fesm2022/yuuvis-client-components-popout.mjs.map +1 -0
  21. package/fesm2022/yuuvis-client-components-split-view.mjs +317 -0
  22. package/fesm2022/yuuvis-client-components-split-view.mjs.map +1 -0
  23. package/fesm2022/yuuvis-client-components-widget-grid.mjs +933 -0
  24. package/fesm2022/yuuvis-client-components-widget-grid.mjs.map +1 -0
  25. package/fesm2022/yuuvis-client-components.mjs +18 -0
  26. package/fesm2022/yuuvis-client-components.mjs.map +1 -0
  27. package/lib/assets/i18n/de.json +56 -0
  28. package/lib/assets/i18n/en.json +56 -0
  29. package/list/README.md +3 -0
  30. package/master-details/README.md +3 -0
  31. package/overflow-hidden/README.md +3 -0
  32. package/overflow-menu/README.md +3 -0
  33. package/package.json +67 -0
  34. package/popout/README.md +3 -0
  35. package/split-view/README.md +3 -0
  36. package/types/yuuvis-client-components-autocomplete.d.ts +89 -0
  37. package/types/yuuvis-client-components-common.d.ts +536 -0
  38. package/types/yuuvis-client-components-datepicker.d.ts +94 -0
  39. package/types/yuuvis-client-components-list.d.ts +380 -0
  40. package/types/yuuvis-client-components-master-details.d.ts +69 -0
  41. package/types/yuuvis-client-components-overflow-hidden.d.ts +72 -0
  42. package/types/yuuvis-client-components-overflow-menu.d.ts +89 -0
  43. package/types/yuuvis-client-components-popout.d.ts +106 -0
  44. package/types/yuuvis-client-components-split-view.d.ts +197 -0
  45. package/types/yuuvis-client-components-widget-grid.d.ts +299 -0
  46. package/types/yuuvis-client-components.d.ts +8 -0
  47. package/widget-grid/README.md +48 -0
@@ -0,0 +1,1724 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, ChangeDetectionStrategy, Component, inject, computed, DestroyRef, viewChild, signal, afterNextRender, ElementRef, Input, Directive, output, EnvironmentInjector, ViewContainerRef, effect, NgZone, DOCUMENT, contentChildren, Injectable, forwardRef, Renderer2, NgModule, InjectionToken, makeEnvironmentProviders, RendererFactory2 } from '@angular/core';
3
+ import { MatButtonModule } from '@angular/material/button';
4
+ import * as i1 from '@angular/material/dialog';
5
+ import { MatDialogActions, MatDialogTitle, MatDialogContent, MAT_DIALOG_DATA, MatDialogModule, MatDialog } from '@angular/material/dialog';
6
+ import { TranslatePipe, AppCacheService, RetentionService, LocaleDatePipe } from '@yuuvis/client-core';
7
+ import { YmtButtonDirective, YmtIconButtonDirective } from '@yuuvis/material';
8
+ import * as i1$1 from '@angular/material/icon';
9
+ import { MatIconModule } from '@angular/material/icon';
10
+ import { TranslatePipe as TranslatePipe$1, TranslateService } from '@ngx-translate/core';
11
+ import { MatProgressSpinner } from '@angular/material/progress-spinner';
12
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
13
+ import { Subject, fromEvent, merge, timer, of, forkJoin, map as map$1 } from 'rxjs';
14
+ import { debounceTime, tap, filter, switchMap, map, takeUntil } from 'rxjs/operators';
15
+ import { NG_VALUE_ACCESSOR, NgControl, FormControlDirective, FormControlName, NgModel } from '@angular/forms';
16
+ import { marker } from '@colsen1991/ngx-translate-extract-marker';
17
+ import { coerceBooleanProperty } from '@angular/cdk/coercion';
18
+
19
+ class DialogComponent {
20
+ headertitle = input(null, ...(ngDevMode ? [{ debugName: "headertitle" }] : /* istanbul ignore next */ []));
21
+ /**
22
+ * @deprecated use headertitle instead
23
+ */
24
+ headertitel = input(null, ...(ngDevMode ? [{ debugName: "headertitel" }] : /* istanbul ignore next */ []));
25
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
26
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DialogComponent, isStandalone: true, selector: "yuv-dialog", inputs: { headertitle: { classPropertyName: "headertitle", publicName: "headertitle", isSignal: true, isRequired: false, transformFunction: null }, headertitel: { classPropertyName: "headertitel", publicName: "headertitel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
27
+ @let title = headertitle() ?? headertitel();
28
+ @if (title) {
29
+ <h2 mat-dialog-title>{{ title }}</h2>
30
+ }
31
+ <mat-dialog-content>
32
+ <ng-content select="main" />
33
+ </mat-dialog-content>
34
+
35
+ <mat-dialog-actions align="end" class="footer">
36
+ <ng-content select="footer" />
37
+ </mat-dialog-actions>
38
+ `, isInline: true, styles: [":host{--_dialog-area-background: var(--dialog-area-background, light-dark(var(--ymt-surface), var(--ymt-surface-container-low)));--_dialog-area-divider-color: var(--dialog-area-divider-color, var(--ymt-outline-variant));height:100%;display:grid;grid-template-rows:auto 1fr auto;grid-template-columns:1fr;grid-template-areas:\"header\" \"content\" \"footer\"}:host h2{grid-area:header;background-color:var(--_dialog-area-background);border-block-end:1px solid var(--_dialog-area-divider-color)}:host .footer{background-color:var(--_dialog-area-background);border-block-start:1px solid var(--_dialog-area-divider-color);gap:calc(var(--ymt-font-body) / 2)}:host.not-separated{background-color:var(--ymt-surface-app)}:host.not-separated h2,:host.not-separated .footer{background-color:transparent;border:0}.mat-mdc-dialog-container{height:100%}mat-dialog-content:empty{display:contents}mat-dialog-content main{display:var(--ymt-dialog-content-display);grid-area:content;overflow:hidden}mat-dialog-actions{grid-area:footer}mat-dialog-actions:empty{display:contents}\n"], dependencies: [{ kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
39
+ }
40
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DialogComponent, decorators: [{
41
+ type: Component,
42
+ args: [{ selector: 'yuv-dialog', imports: [MatDialogActions, MatDialogTitle, MatDialogContent], template: `
43
+ @let title = headertitle() ?? headertitel();
44
+ @if (title) {
45
+ <h2 mat-dialog-title>{{ title }}</h2>
46
+ }
47
+ <mat-dialog-content>
48
+ <ng-content select="main" />
49
+ </mat-dialog-content>
50
+
51
+ <mat-dialog-actions align="end" class="footer">
52
+ <ng-content select="footer" />
53
+ </mat-dialog-actions>
54
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--_dialog-area-background: var(--dialog-area-background, light-dark(var(--ymt-surface), var(--ymt-surface-container-low)));--_dialog-area-divider-color: var(--dialog-area-divider-color, var(--ymt-outline-variant));height:100%;display:grid;grid-template-rows:auto 1fr auto;grid-template-columns:1fr;grid-template-areas:\"header\" \"content\" \"footer\"}:host h2{grid-area:header;background-color:var(--_dialog-area-background);border-block-end:1px solid var(--_dialog-area-divider-color)}:host .footer{background-color:var(--_dialog-area-background);border-block-start:1px solid var(--_dialog-area-divider-color);gap:calc(var(--ymt-font-body) / 2)}:host.not-separated{background-color:var(--ymt-surface-app)}:host.not-separated h2,:host.not-separated .footer{background-color:transparent;border:0}.mat-mdc-dialog-container{height:100%}mat-dialog-content:empty{display:contents}mat-dialog-content main{display:var(--ymt-dialog-content-display);grid-area:content;overflow:hidden}mat-dialog-actions{grid-area:footer}mat-dialog-actions:empty{display:contents}\n"] }]
55
+ }], propDecorators: { headertitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headertitle", required: false }] }], headertitel: [{ type: i0.Input, args: [{ isSignal: true, alias: "headertitel", required: false }] }] } });
56
+
57
+ class ConfirmComponent {
58
+ dialogData = inject(MAT_DIALOG_DATA);
59
+ buttonType = computed(() => {
60
+ switch (this.dialogData.level) {
61
+ case 'warning':
62
+ return 'danger';
63
+ // case 'warning':
64
+ // return 'tertiary' as ButtonType;
65
+ // case 'info':
66
+ // return 'primary' as ButtonType;
67
+ // case 'success':
68
+ // return 'primary' as ButtonType;
69
+ default:
70
+ return 'primary';
71
+ }
72
+ }, ...(ngDevMode ? [{ debugName: "buttonType" }] : /* istanbul ignore next */ []));
73
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ConfirmComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
74
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ConfirmComponent, isStandalone: true, selector: "yuv-confirm", ngImport: i0, template: "<yuv-dialog [headertitle]=\"dialogData.title || ''\">\n <main>{{ dialogData.message }}</main>\n <footer>\n @if (!dialogData.hideCancelButton) {\n <button ymtButton=\"secondary\" mat-dialog-close type=\"button\">\n {{ dialogData.cancelLabel || ('yuv.confirm.cancel' | translate) }}\n </button>\n }\n <button [ymtButton]=\"buttonType()\" type=\"button\" [mat-dialog-close]=\"true\">\n {{ dialogData.confirmLabel || ('yuv.confirm.confirm' | translate) }}\n </button>\n </footer>\n</yuv-dialog>\n", styles: [":host{display:contents}:host main{padding:var(--ymt-spacing-m)}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "component", type: DialogComponent, selector: "yuv-dialog", inputs: ["headertitle", "headertitel"] }, { kind: "directive", type: YmtButtonDirective, selector: "button[ymtButton], a[ymtButton]", inputs: ["ymtButton", "disabled", "aria-disabled", "disableRipple", "disabledInteractive", "button-size"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
75
+ }
76
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ConfirmComponent, decorators: [{
77
+ type: Component,
78
+ args: [{ selector: 'yuv-confirm', imports: [MatButtonModule, TranslatePipe, MatDialogModule, DialogComponent, YmtButtonDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<yuv-dialog [headertitle]=\"dialogData.title || ''\">\n <main>{{ dialogData.message }}</main>\n <footer>\n @if (!dialogData.hideCancelButton) {\n <button ymtButton=\"secondary\" mat-dialog-close type=\"button\">\n {{ dialogData.cancelLabel || ('yuv.confirm.cancel' | translate) }}\n </button>\n }\n <button [ymtButton]=\"buttonType()\" type=\"button\" [mat-dialog-close]=\"true\">\n {{ dialogData.confirmLabel || ('yuv.confirm.confirm' | translate) }}\n </button>\n </footer>\n</yuv-dialog>\n", styles: [":host{display:contents}:host main{padding:var(--ymt-spacing-m)}\n"] }]
79
+ }] });
80
+
81
+ const DEFAULT_SCROLL_AMOUNT$1 = 150;
82
+ /**
83
+ * Wrapper component that adds left/right scroll buttons when its content overflows horizontally.
84
+ * Buttons appear only when there is overflow in the respective direction.
85
+ *
86
+ * @example
87
+ * <yuv-scroll-buttons>
88
+ * <mat-chip-grid>...</mat-chip-grid>
89
+ * </yuv-scroll-buttons>
90
+ */
91
+ class ScrollButtonsComponent {
92
+ #destroyRef = inject(DestroyRef);
93
+ scrollContainer = viewChild.required('scrollContainer');
94
+ /** How many pixels to scroll per button click. */
95
+ scrollAmount = input(DEFAULT_SCROLL_AMOUNT$1, ...(ngDevMode ? [{ debugName: "scrollAmount" }] : /* istanbul ignore next */ []));
96
+ showLeftButton = signal(false, ...(ngDevMode ? [{ debugName: "showLeftButton" }] : /* istanbul ignore next */ []));
97
+ showRightButton = signal(false, ...(ngDevMode ? [{ debugName: "showRightButton" }] : /* istanbul ignore next */ []));
98
+ #resizeObserver;
99
+ constructor() {
100
+ afterNextRender(() => {
101
+ const element = this.scrollContainer().nativeElement;
102
+ this.#resizeObserver = new ResizeObserver(() => this.#checkOverflow());
103
+ this.#resizeObserver.observe(element);
104
+ // Also observe mutations to detect when children (chips) are added/removed
105
+ const mutationObserver = new MutationObserver(() => this.#checkOverflow());
106
+ mutationObserver.observe(element, { childList: true, subtree: true });
107
+ this.#destroyRef.onDestroy(() => {
108
+ this.#resizeObserver?.disconnect();
109
+ mutationObserver.disconnect();
110
+ });
111
+ this.#checkOverflow();
112
+ });
113
+ }
114
+ onScroll() {
115
+ this.#checkOverflow();
116
+ }
117
+ scrollLeft() {
118
+ this.scrollContainer().nativeElement.scrollBy({ left: -this.scrollAmount(), behavior: 'smooth' });
119
+ }
120
+ scrollRight() {
121
+ this.scrollContainer().nativeElement.scrollBy({ left: this.scrollAmount(), behavior: 'smooth' });
122
+ }
123
+ #checkOverflow() {
124
+ const element = this.scrollContainer().nativeElement;
125
+ const threshold = 1; // account for sub-pixel rounding
126
+ this.showLeftButton.set(element.scrollLeft > threshold);
127
+ this.showRightButton.set(element.scrollLeft + element.clientWidth < element.scrollWidth - threshold);
128
+ }
129
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ScrollButtonsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
130
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ScrollButtonsComponent, isStandalone: true, selector: "yuv-scroll-buttons", inputs: { scrollAmount: { classPropertyName: "scrollAmount", publicName: "scrollAmount", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (showLeftButton()) {\n <button\n class=\"scroll-btn scroll-btn--left\"\n (click)=\"scrollLeft()\"\n [attr.aria-label]=\"'yuv.object-form-element.scroll.button.left' | translate\"\n >\n <mat-icon>chevron_left</mat-icon>\n </button>\n}\n<div class=\"scroll-content\" #scrollContainer (scroll)=\"onScroll()\">\n <ng-content />\n</div>\n@if (showRightButton()) {\n <button\n class=\"scroll-btn scroll-btn--right\"\n (click)=\"scrollRight()\"\n [attr.aria-label]=\"'yuv.object-form-element.scroll.button.right' | translate\"\n >\n <mat-icon>chevron_right</mat-icon>\n </button>\n}\n", styles: [":host{display:flex;align-items:center;flex:1;min-width:0}.scroll-content{flex:1;overflow-x:auto;scrollbar-width:none;min-width:0}.scroll-content ::ng-deep mat-chip-grid .mdc-evolution-chip-set__chips{flex-wrap:nowrap}.scroll-content ::ng-deep mat-chip-grid mat-chip-row{flex-shrink:0;max-width:none}.scroll-content ::ng-deep mat-chip-grid mat-chip-row .mdc-evolution-chip__cell--primary{max-width:none}.scroll-content ::ng-deep mat-chip-grid mat-chip-row .mdc-evolution-chip__text-label,.scroll-content ::ng-deep mat-chip-grid mat-chip-row .mat-mdc-chip-action-label{white-space:nowrap}.scroll-btn{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:0;color:var(--ymv-text-color-subtle);width:var(--ymv-sizing-m);height:var(--ymv-sizing-m);background-color:var(--ymv-surface-hover)}.scroll-btn:hover,.scroll-btn:focus-visible{color:var(--ymv-text-color)}.scroll-btn:focus-visible{outline:2px solid var(--ymv-primary-color);outline-offset:-2px}.scroll-btn mat-icon{font-size:20px;width:20px;height:20px}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: TranslatePipe$1, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
131
+ }
132
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ScrollButtonsComponent, decorators: [{
133
+ type: Component,
134
+ args: [{ selector: 'yuv-scroll-buttons', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatIconModule, TranslatePipe$1], template: "@if (showLeftButton()) {\n <button\n class=\"scroll-btn scroll-btn--left\"\n (click)=\"scrollLeft()\"\n [attr.aria-label]=\"'yuv.object-form-element.scroll.button.left' | translate\"\n >\n <mat-icon>chevron_left</mat-icon>\n </button>\n}\n<div class=\"scroll-content\" #scrollContainer (scroll)=\"onScroll()\">\n <ng-content />\n</div>\n@if (showRightButton()) {\n <button\n class=\"scroll-btn scroll-btn--right\"\n (click)=\"scrollRight()\"\n [attr.aria-label]=\"'yuv.object-form-element.scroll.button.right' | translate\"\n >\n <mat-icon>chevron_right</mat-icon>\n </button>\n}\n", styles: [":host{display:flex;align-items:center;flex:1;min-width:0}.scroll-content{flex:1;overflow-x:auto;scrollbar-width:none;min-width:0}.scroll-content ::ng-deep mat-chip-grid .mdc-evolution-chip-set__chips{flex-wrap:nowrap}.scroll-content ::ng-deep mat-chip-grid mat-chip-row{flex-shrink:0;max-width:none}.scroll-content ::ng-deep mat-chip-grid mat-chip-row .mdc-evolution-chip__cell--primary{max-width:none}.scroll-content ::ng-deep mat-chip-grid mat-chip-row .mdc-evolution-chip__text-label,.scroll-content ::ng-deep mat-chip-grid mat-chip-row .mat-mdc-chip-action-label{white-space:nowrap}.scroll-btn{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:0;color:var(--ymv-text-color-subtle);width:var(--ymv-sizing-m);height:var(--ymv-sizing-m);background-color:var(--ymv-surface-hover)}.scroll-btn:hover,.scroll-btn:focus-visible{color:var(--ymv-text-color)}.scroll-btn:focus-visible{outline:2px solid var(--ymv-primary-color);outline-offset:-2px}.scroll-btn mat-icon{font-size:20px;width:20px;height:20px}\n"] }]
135
+ }], ctorParameters: () => [], propDecorators: { scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }], scrollAmount: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollAmount", required: false }] }] } });
136
+
137
+ /**
138
+ * Directive putting focus on a 'focusable' child element.
139
+ * By default the first focusable child will receive focus.
140
+ */
141
+ class AutofocusChildDirective {
142
+ #elRef = inject(ElementRef);
143
+ #targetIndex = 0;
144
+ set yuvAutofocusChild(s) {
145
+ const i = parseInt(s);
146
+ this.#targetIndex = !isNaN(i) ? i : 0;
147
+ }
148
+ #getFirstFocusableChild() {
149
+ const focusableElements = [
150
+ ...this.#elRef.nativeElement.querySelectorAll('a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])')
151
+ ].filter((el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
152
+ return focusableElements[this.#targetIndex];
153
+ }
154
+ ngAfterViewInit() {
155
+ setTimeout(() => {
156
+ const focusEl = this.#getFirstFocusableChild();
157
+ if (focusEl)
158
+ focusEl.focus();
159
+ });
160
+ }
161
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AutofocusChildDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
162
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AutofocusChildDirective, isStandalone: true, selector: "[yuvAutofocusChild]", inputs: { yuvAutofocusChild: "yuvAutofocusChild" }, ngImport: i0 });
163
+ }
164
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AutofocusChildDirective, decorators: [{
165
+ type: Directive,
166
+ args: [{
167
+ selector: '[yuvAutofocusChild]',
168
+ standalone: true
169
+ }]
170
+ }], propDecorators: { yuvAutofocusChild: [{
171
+ type: Input
172
+ }] } });
173
+
174
+ /**
175
+ * Directive putting delayed focus on an element. If no
176
+ * delay is set, the focus will be set immediately.
177
+ *
178
+ * You have to ensure that the element is focusable when adding this directive to it.
179
+ */
180
+ class AutofocusDelayedDirective {
181
+ #elRef = inject(ElementRef);
182
+ /**
183
+ * Sets the delay in milliseconds. If no delay is set, the focus will be set immediately.
184
+ */
185
+ yuvAutofocusDelayed = input(0, ...(ngDevMode ? [{ debugName: "yuvAutofocusDelayed" }] : /* istanbul ignore next */ []));
186
+ #delay = computed(() => {
187
+ const d = this.yuvAutofocusDelayed();
188
+ return typeof d === 'string' ? parseInt(d) : d;
189
+ }, ...(ngDevMode ? [{ debugName: "#delay" }] : /* istanbul ignore next */ []));
190
+ ngAfterViewInit() {
191
+ setTimeout(() => {
192
+ this.#elRef.nativeElement.focus();
193
+ }, this.#delay());
194
+ }
195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AutofocusDelayedDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
196
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: AutofocusDelayedDirective, isStandalone: true, selector: "[yuvAutofocusDelayed]", inputs: { yuvAutofocusDelayed: { classPropertyName: "yuvAutofocusDelayed", publicName: "yuvAutofocusDelayed", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
197
+ }
198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AutofocusDelayedDirective, decorators: [{
199
+ type: Directive,
200
+ args: [{
201
+ selector: '[yuvAutofocusDelayed]',
202
+ standalone: true
203
+ }]
204
+ }], propDecorators: { yuvAutofocusDelayed: [{ type: i0.Input, args: [{ isSignal: true, alias: "yuvAutofocusDelayed", required: false }] }] } });
205
+
206
+ class BusyOverlayComponent {
207
+ config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
208
+ error = input(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
209
+ errorDismiss = output();
210
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BusyOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
211
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: BusyOverlayComponent, isStandalone: true, selector: "yuv-busy-overlay", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { errorDismiss: "errorDismiss" }, host: { properties: { "class.error": "!!error()", "class.blured": "!!config()?.blur", "style.--backdrop-background": "config()?.backdropColor || \"var(--ymt-surface-app)\"" } }, ngImport: i0, template: "<div class=\"backdrop\"></div>\n\n@let e = error();\n@if (e) {\n <div class=\"error\">\n <button ymtIconButton icon-button-size=\"small\" (click)=\"errorDismiss.emit()\">\n <mat-icon>close</mat-icon>\n </button>\n <p>{{ e }}</p>\n </div>\n} @else {\n <mat-progress-spinner class=\"ymt-progress-spinner--giant\" [mode]=\"'indeterminate'\"></mat-progress-spinner>\n}\n", styles: [":host{position:absolute;transition:opacity .2s;inset:0;display:grid;place-items:center;z-index:5}:host.blured{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}:host{--mat-progress-spinner-active-indicator-color: currentColor}:host .error,:host mat-progress-spinner{opacity:0;animation:fadeIn .2s ease-in forwards;animation-delay:.2s}:host .error{display:flex;flex-direction:column;background-color:var(--ymt-danger-container);color:var(--ymt-on-danger-container);border:var(--ymt-danger);padding:var(--ymt-spacing-xs);border-radius:var(--ymt-corner-xs);max-width:60ch;margin:var(--ymt-spacing-m);overflow-y:auto;align-items:end}:host .error p{margin:var(--ymt-spacing-m)}:host .backdrop{position:absolute;inset:0;background-color:rgb(from var(--backdrop-background) r g b/.5);animation:fadeIn .2s ease-in}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }] });
212
+ }
213
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BusyOverlayComponent, decorators: [{
214
+ type: Component,
215
+ args: [{ selector: 'yuv-busy-overlay', imports: [YmtIconButtonDirective, MatIconModule, MatProgressSpinner], host: {
216
+ '[class.error]': '!!error()',
217
+ '[class.blured]': '!!config()?.blur',
218
+ '[style.--backdrop-background]': 'config()?.backdropColor || "var(--ymt-surface-app)"'
219
+ }, template: "<div class=\"backdrop\"></div>\n\n@let e = error();\n@if (e) {\n <div class=\"error\">\n <button ymtIconButton icon-button-size=\"small\" (click)=\"errorDismiss.emit()\">\n <mat-icon>close</mat-icon>\n </button>\n <p>{{ e }}</p>\n </div>\n} @else {\n <mat-progress-spinner class=\"ymt-progress-spinner--giant\" [mode]=\"'indeterminate'\"></mat-progress-spinner>\n}\n", styles: [":host{position:absolute;transition:opacity .2s;inset:0;display:grid;place-items:center;z-index:5}:host.blured{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}:host{--mat-progress-spinner-active-indicator-color: currentColor}:host .error,:host mat-progress-spinner{opacity:0;animation:fadeIn .2s ease-in forwards;animation-delay:.2s}:host .error{display:flex;flex-direction:column;background-color:var(--ymt-danger-container);color:var(--ymt-on-danger-container);border:var(--ymt-danger);padding:var(--ymt-spacing-xs);border-radius:var(--ymt-corner-xs);max-width:60ch;margin:var(--ymt-spacing-m);overflow-y:auto;align-items:end}:host .error p{margin:var(--ymt-spacing-m)}:host .backdrop{position:absolute;inset:0;background-color:rgb(from var(--backdrop-background) r g b/.5);animation:fadeIn .2s ease-in}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
220
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], errorDismiss: [{ type: i0.Output, args: ["errorDismiss"] }] } });
221
+
222
+ /**
223
+ * A directive that will overlay its host component with a translucent background
224
+ * and a loading spinner once the condition resolves with true. This is useful for example to
225
+ * prevent user interaction while component data is loading or some processing is done.
226
+ *
227
+ * It'll also prevent the overlaid element from being interacted with.
228
+ *
229
+ * ```html
230
+ * <div class="data-panel" [yuvBusyOverlay]="isLoadingData">...</div>
231
+ * ```
232
+ * Setting a `yuvBusyError` will replace the loading spinner by the provided error message.
233
+ * You need to keep the `yuvBusyOverlay` condition true to show the error.
234
+ * The error element rendered has a dismiss/close action that trigger the `yuvBusyErrorDismiss`
235
+ * output event, that you then can use to set the busy condition to false.
236
+ *
237
+ * ```html
238
+ * <div class="result-list"
239
+ * [yuvBusyOverlay]="waitingForServerResponse"
240
+ * (yuvBusyErrorDismiss)="waitingForServerResponse = false"
241
+ * [yuvBusyError]="errorMessage">
242
+ * ...
243
+ * </div>
244
+ * ```
245
+ *
246
+ * ```ts
247
+ * // in your component code
248
+ * this.waitingForServerResponse = true;
249
+ * fetchData().subscribe({
250
+ * next: (data) => {
251
+ * ...
252
+ * this.waitingForServerResponse = false;
253
+ * }
254
+ * error: (err) => {
255
+ * this.errorMessage = 'Failed to load data from server: ' + err.message;
256
+ * }
257
+ * });
258
+ * ```
259
+ *
260
+ */
261
+ class BusyOverlayDirective {
262
+ #initialStylePosition = signal('initial', ...(ngDevMode ? [{ debugName: "#initialStylePosition" }] : /* istanbul ignore next */ []));
263
+ stylePosition = signal(this.#initialStylePosition(), ...(ngDevMode ? [{ debugName: "stylePosition" }] : /* istanbul ignore next */ []));
264
+ #elRef = inject(ElementRef);
265
+ #environmentInjector = inject(EnvironmentInjector);
266
+ #vcRef = inject(ViewContainerRef);
267
+ /**
268
+ * The Boolean expression to evaluate as the condition for showing the busy overlay
269
+ */
270
+ yuvBusyOverlay = input(false, ...(ngDevMode ? [{ debugName: "yuvBusyOverlay" }] : /* istanbul ignore next */ []));
271
+ #yuvBusyOverlayEffect = effect(() => {
272
+ const busy = this.yuvBusyOverlay();
273
+ if (busy === true) {
274
+ this.#addBusyOverlay();
275
+ }
276
+ else {
277
+ this.#removeBusyOverlay();
278
+ }
279
+ }, ...(ngDevMode ? [{ debugName: "#yuvBusyOverlayEffect" }] : /* istanbul ignore next */ []));
280
+ /**
281
+ * Configuration options for the busy overlay. These include
282
+ * e.g. backdrops background color and whether to blur the
283
+ * overlaid content.
284
+ */
285
+ yuvBusyOverlayConfig = input(...(ngDevMode ? [undefined, { debugName: "yuvBusyOverlayConfig" }] : /* istanbul ignore next */ []));
286
+ /**
287
+ * Error message to display in the overlay. If set, the loading spinner is replaced by the error message.
288
+ */
289
+ yuvBusyError = input(...(ngDevMode ? [undefined, { debugName: "yuvBusyError" }] : /* istanbul ignore next */ []));
290
+ #yuvBusyErrorEffect = effect(() => this.#busyOverlayComponentRef?.setInput('error', this.yuvBusyError()), ...(ngDevMode ? [{ debugName: "#yuvBusyErrorEffect" }] : /* istanbul ignore next */ []));
291
+ /**
292
+ * Event emitted when the error message's dismiss action is triggered.
293
+ */
294
+ yuvBusyErrorDismiss = output();
295
+ #busyOverlayComponentRef;
296
+ #outputSubscriptions = [];
297
+ #attachOverlay() {
298
+ if (this.#busyOverlayComponentRef) {
299
+ // Already attached; just refresh inputs/outputs
300
+ this.#applyInputs();
301
+ return;
302
+ }
303
+ // Create the component inside the directive’s host
304
+ this.#busyOverlayComponentRef = this.#vcRef.createComponent(BusyOverlayComponent, {
305
+ environmentInjector: this.#environmentInjector
306
+ });
307
+ const node = this.#busyOverlayComponentRef.location.nativeElement;
308
+ this.#elRef.nativeElement.appendChild(node);
309
+ this.#applyInputs();
310
+ this.#bindOutputs();
311
+ this.#busyOverlayComponentRef.changeDetectorRef?.detectChanges?.();
312
+ }
313
+ #detachOverlay() {
314
+ // Unsubscribe outputs first
315
+ this.#outputSubscriptions.forEach((sub) => sub.unsubscribe());
316
+ this.#outputSubscriptions = [];
317
+ // Destroy componentRef and clear container
318
+ if (this.#busyOverlayComponentRef) {
319
+ this.#busyOverlayComponentRef.destroy();
320
+ this.#busyOverlayComponentRef = undefined;
321
+ }
322
+ this.#vcRef.clear();
323
+ }
324
+ #applyInputs() {
325
+ if (!this.#busyOverlayComponentRef)
326
+ return;
327
+ this.#busyOverlayComponentRef.setInput('config', this.yuvBusyOverlayConfig());
328
+ this.#busyOverlayComponentRef.setInput('error', this.yuvBusyError());
329
+ }
330
+ #bindOutputs() {
331
+ if (!this.#busyOverlayComponentRef)
332
+ return;
333
+ // Clean up old subscriptions if re-binding
334
+ this.#outputSubscriptions.forEach((sub) => sub.unsubscribe());
335
+ this.#outputSubscriptions = [];
336
+ const errorDismissOutput = this.#busyOverlayComponentRef.instance.errorDismiss;
337
+ this.#outputSubscriptions.push(errorDismissOutput.subscribe(() => this.yuvBusyErrorDismiss.emit()));
338
+ }
339
+ #addBusyOverlay() {
340
+ this.stylePosition.set('relative');
341
+ this.#attachOverlay();
342
+ }
343
+ #removeBusyOverlay() {
344
+ if (!this.#busyOverlayComponentRef)
345
+ return;
346
+ const el = this.#busyOverlayComponentRef.location.nativeElement;
347
+ if (el) {
348
+ el.style.opacity = '0';
349
+ setTimeout(() => {
350
+ this.#detachOverlay();
351
+ this.stylePosition.set(this.#initialStylePosition());
352
+ }, 200);
353
+ }
354
+ }
355
+ ngOnInit() {
356
+ const initialPosition = getComputedStyle(this.#elRef.nativeElement).position;
357
+ this.#initialStylePosition.set(initialPosition === 'static' || !initialPosition ? 'relative' : initialPosition);
358
+ }
359
+ ngOnDestroy() {
360
+ this.#detachOverlay();
361
+ }
362
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BusyOverlayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
363
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: BusyOverlayDirective, isStandalone: true, selector: "[yuvBusyOverlay]", inputs: { yuvBusyOverlay: { classPropertyName: "yuvBusyOverlay", publicName: "yuvBusyOverlay", isSignal: true, isRequired: false, transformFunction: null }, yuvBusyOverlayConfig: { classPropertyName: "yuvBusyOverlayConfig", publicName: "yuvBusyOverlayConfig", isSignal: true, isRequired: false, transformFunction: null }, yuvBusyError: { classPropertyName: "yuvBusyError", publicName: "yuvBusyError", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { yuvBusyErrorDismiss: "yuvBusyErrorDismiss" }, host: { properties: { "attr.aria-busy": "yuvBusyOverlay() ? \"true\" : \"false\"", "style.position": "stylePosition()" } }, ngImport: i0 });
364
+ }
365
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BusyOverlayDirective, decorators: [{
366
+ type: Directive,
367
+ args: [{
368
+ selector: '[yuvBusyOverlay]',
369
+ standalone: true,
370
+ host: {
371
+ '[attr.aria-busy]': 'yuvBusyOverlay() ? "true" : "false"',
372
+ '[style.position]': 'stylePosition()'
373
+ }
374
+ }]
375
+ }], propDecorators: { yuvBusyOverlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "yuvBusyOverlay", required: false }] }], yuvBusyOverlayConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "yuvBusyOverlayConfig", required: false }] }], yuvBusyError: [{ type: i0.Input, args: [{ isSignal: true, alias: "yuvBusyError", required: false }] }], yuvBusyErrorDismiss: [{ type: i0.Output, args: ["yuvBusyErrorDismiss"] }] } });
376
+
377
+ /**
378
+ * Fixes the issue of 'click' event beeing triggered on 'doubleclick' by defining new outputs that
379
+ * distinguish between single and double click.
380
+ */
381
+ class ClickDoubleDirective {
382
+ debounceTime = input(200, ...(ngDevMode ? [{ debugName: "debounceTime" }] : /* istanbul ignore next */ []));
383
+ doubleClick = output({ alias: 'click.double' });
384
+ singleClick = output({ alias: 'click.single' });
385
+ clicksSubject = new Subject();
386
+ constructor() {
387
+ this.clicksSubject.pipe(takeUntilDestroyed(), debounceTime(this.debounceTime())).subscribe({
388
+ next: (event) => {
389
+ event.type === 'click' ? this.singleClick.emit(event) : this.doubleClick.emit(event);
390
+ }
391
+ });
392
+ }
393
+ clickEvent(event) {
394
+ event.preventDefault();
395
+ event.stopPropagation();
396
+ this.clicksSubject.next(event);
397
+ }
398
+ doubleClickEvent(event) {
399
+ event.preventDefault();
400
+ event.stopPropagation();
401
+ this.clicksSubject.next(event);
402
+ }
403
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ClickDoubleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
404
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: ClickDoubleDirective, isStandalone: true, selector: "[click.single],[click.double]", inputs: { debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { doubleClick: "click.double", singleClick: "click.single" }, host: { listeners: { "click": "clickEvent($event)", "dblclick": "doubleClickEvent($event)" } }, ngImport: i0 });
405
+ }
406
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ClickDoubleDirective, decorators: [{
407
+ type: Directive,
408
+ args: [{
409
+ selector: '[click.single],[click.double]',
410
+ host: {
411
+ '(click)': 'clickEvent($event)',
412
+ '(dblclick)': 'doubleClickEvent($event)'
413
+ }
414
+ }]
415
+ }], ctorParameters: () => [], propDecorators: { debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }], doubleClick: [{ type: i0.Output, args: ["click.double"] }], singleClick: [{ type: i0.Output, args: ["click.single"] }] } });
416
+
417
+ /**
418
+ * Directive to watch the size of an element inside the DOM. Usefull for example to provide
419
+ * a different layout (different components) depending on the available screen estate. You
420
+ * should first try to use CSS container queries but somtimes you need a different set of
421
+ * components to be loaded for a certain component size.
422
+ *
423
+ * Let's say you have components designed for bigger screens. You do not want to load them
424
+ * if there is not enough space for them. So you rather load components that are designed to
425
+ * take less space by providing the best user experience on smaller devices.
426
+ *
427
+ * ```html
428
+ * <div yuvContainerSize (containerHeight)="onContainerResize($event)" (containerWidth)="onContainerResize($event)"></div>
429
+ * ```
430
+ *
431
+ */
432
+ class ContainerSizeDirective {
433
+ elRef = inject(ElementRef);
434
+ ngZone = inject(NgZone);
435
+ containerHeight = output();
436
+ containerWidth = output();
437
+ _size;
438
+ _resizeObserver = new ResizeObserver((entries) => {
439
+ const size = entries[0].borderBoxSize[0];
440
+ if (!this._size || size.blockSize !== this._size.blockSize) {
441
+ this._emit(size.blockSize, true);
442
+ }
443
+ if (!this._size || size.inlineSize !== this._size.inlineSize) {
444
+ this._emit(size.inlineSize);
445
+ }
446
+ this._size = size;
447
+ });
448
+ constructor() {
449
+ this._resizeObserver.observe(this.elRef.nativeElement);
450
+ }
451
+ _emit(value, isHeight = false) {
452
+ // ResizeObserver callback is not covered by change detection
453
+ // so it has to be executed withing ngZone
454
+ this.ngZone.run(() => {
455
+ (isHeight ? this.containerHeight : this.containerWidth).emit(value);
456
+ });
457
+ }
458
+ ngOnDestroy() {
459
+ this._resizeObserver.unobserve(this.elRef.nativeElement);
460
+ }
461
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ContainerSizeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
462
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: ContainerSizeDirective, isStandalone: true, selector: "[yuvContainerSize]", outputs: { containerHeight: "containerHeight", containerWidth: "containerWidth" }, ngImport: i0 });
463
+ }
464
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ContainerSizeDirective, decorators: [{
465
+ type: Directive,
466
+ args: [{
467
+ selector: '[yuvContainerSize]',
468
+ standalone: true
469
+ }]
470
+ }], ctorParameters: () => [], propDecorators: { containerHeight: [{ type: i0.Output, args: ["containerHeight"] }], containerWidth: [{ type: i0.Output, args: ["containerWidth"] }] } });
471
+
472
+ /**
473
+ * Directive for adding drag scroll behaviour to a container element. Elements that overlow will then
474
+ * be 'scrollable' by dragging the list of children.
475
+ *
476
+ * @example
477
+ * <div yuvDragScroll>
478
+ * <div class="tile">#1</div>
479
+ * <div class="tile">#2</div>
480
+ * <div class="tile">#3</div>
481
+ * ...
482
+ * </div>
483
+ */
484
+ class DragScrollDirective {
485
+ #document = inject(DOCUMENT);
486
+ #element = inject(ElementRef);
487
+ #dragging = false;
488
+ #applyDraggingStyles(el, remove) {
489
+ const draggingStyles = {
490
+ cursor: 'grabbing'
491
+ };
492
+ Object.keys(draggingStyles).forEach((property) => {
493
+ if (remove) {
494
+ el.style.removeProperty(property);
495
+ }
496
+ else {
497
+ el.style.setProperty(property, draggingStyles[property]);
498
+ }
499
+ });
500
+ }
501
+ ngAfterViewInit() {
502
+ const nativeElement = this.#element.nativeElement;
503
+ const mouseDown$ = fromEvent(nativeElement, 'mousedown');
504
+ const mouseMove$ = fromEvent(this.#document, 'mousemove');
505
+ const mouseUp$ = fromEvent(this.#document, 'mouseup').pipe(tap((e) => {
506
+ if (this.#dragging) {
507
+ e.preventDefault();
508
+ e.stopPropagation();
509
+ this.#dragging = false;
510
+ this.#applyDraggingStyles(nativeElement, true);
511
+ }
512
+ }));
513
+ const dragMove$ = mouseDown$.pipe(filter(() =>
514
+ // only calculate new scroll position if the container is actually overflowing
515
+ nativeElement.scrollHeight > nativeElement.clientHeight || nativeElement.scrollWidth > nativeElement.clientWidth), tap((startEvent) => {
516
+ this.#applyDraggingStyles(nativeElement);
517
+ startEvent.preventDefault();
518
+ startEvent.stopPropagation();
519
+ }), switchMap((startEvent) => {
520
+ this.#dragging = true;
521
+ const scrollPos = {
522
+ left: nativeElement.scrollLeft,
523
+ top: nativeElement.scrollTop
524
+ };
525
+ return mouseMove$.pipe(map((moveEvent) => {
526
+ moveEvent.preventDefault();
527
+ moveEvent.stopPropagation();
528
+ return {
529
+ startEvent,
530
+ moveEvent,
531
+ scrollPos
532
+ };
533
+ }), takeUntil(mouseUp$));
534
+ }), tap(({ startEvent, moveEvent, scrollPos }) => {
535
+ const diffX = moveEvent.clientX - startEvent.clientX;
536
+ const diffY = moveEvent.clientY - startEvent.clientY;
537
+ nativeElement.scrollLeft = scrollPos.left - diffX;
538
+ nativeElement.scrollTop = scrollPos.top - diffY;
539
+ }));
540
+ dragMove$.subscribe();
541
+ }
542
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DragScrollDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
543
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: DragScrollDirective, isStandalone: true, selector: "[yuvDragScroll]", ngImport: i0 });
544
+ }
545
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DragScrollDirective, decorators: [{
546
+ type: Directive,
547
+ args: [{
548
+ selector: '[yuvDragScroll]'
549
+ }]
550
+ }] });
551
+
552
+ class DragSelectDirective {
553
+ #selector;
554
+ #selectStartX = 0;
555
+ #selectStartY = 0;
556
+ #selection = [];
557
+ onPointerDown(event) {
558
+ if (this.yuvDragSelect()?.disabled || event.target.tagName === 'BUTTON')
559
+ return;
560
+ event.preventDefault();
561
+ this.#selectStartX = event.pageX;
562
+ this.#selectStartY = event.pageY;
563
+ const div = document.createElement('div');
564
+ div.style.position = 'absolute';
565
+ div.style.width = '0';
566
+ div.style.height = '0';
567
+ div.style.left = this.#selectStartX + 'px';
568
+ div.style.top = this.#selectStartY + 'px';
569
+ div.classList.add('drag-select');
570
+ this.#selector = div;
571
+ document.body.append(this.#selector);
572
+ this.#selection = [];
573
+ addEventListener('pointermove', this.#resize);
574
+ addEventListener('pointerup', this.#onPointerUp);
575
+ }
576
+ items = contentChildren(DragSelectItemDirective, ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
577
+ #selectables = computed(() => this.items().map((item) => item.el), ...(ngDevMode ? [{ debugName: "#selectables" }] : /* istanbul ignore next */ []));
578
+ yuvDragSelect = input(...(ngDevMode ? [undefined, { debugName: "yuvDragSelect" }] : /* istanbul ignore next */ []));
579
+ dragSelectChange = output();
580
+ dragSelect = output();
581
+ #onPointerUp = () => {
582
+ removeEventListener('pointermove', this.#resize);
583
+ removeEventListener('pointerup', this.#onPointerUp);
584
+ if (this.#selection.length)
585
+ this.dragSelect.emit(this.#selection);
586
+ if (this.#selector)
587
+ this.#selector.remove();
588
+ };
589
+ #resize = (event) => {
590
+ if (!this.#selector)
591
+ return;
592
+ const diffX = event.pageX - this.#selectStartX;
593
+ const diffY = event.pageY - this.#selectStartY;
594
+ this.#selector.style.left = diffX < 0 ? this.#selectStartX + diffX + 'px' : this.#selectStartX + 'px';
595
+ this.#selector.style.top = diffY < 0 ? this.#selectStartY + diffY + 'px' : this.#selectStartY + 'px';
596
+ this.#selector.style.height = Math.abs(diffY) + 'px';
597
+ this.#selector.style.width = Math.abs(diffX) + 'px';
598
+ this.#selector.style.border = `1px solid ${this.yuvDragSelect()?.selectorColor || 'var(--ymt-primary'}`;
599
+ this.#checkSelected();
600
+ };
601
+ #checkSelected = () => {
602
+ if (!this.#selector)
603
+ return;
604
+ const select = this.#selector.getBoundingClientRect();
605
+ const { x, y, height, width } = select;
606
+ if (!height || !width)
607
+ return;
608
+ const currSelectionLength = this.#selection.length;
609
+ this.#selectables().forEach((selectable, idx) => {
610
+ const r1 = { x: x + window.scrollX, y: y + window.scrollY, height, width };
611
+ const r2 = selectable.getBoundingClientRect();
612
+ this.#selection = this.#selection.filter((s) => s !== idx);
613
+ if (this.#checkRectIntersection(r1, r2)) {
614
+ this.#selection.push(idx);
615
+ }
616
+ });
617
+ if (currSelectionLength !== this.#selection.length) {
618
+ this.dragSelectChange.emit(this.#selection);
619
+ }
620
+ };
621
+ #checkRectIntersection = (r1, r2) => {
622
+ return !(r1.x + r1.width < r2.x || r2.x + r2.width < r1.x || r1.y + r1.height < r2.y || r2.y + r2.height < r1.y);
623
+ };
624
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DragSelectDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
625
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.2.9", type: DragSelectDirective, isStandalone: true, selector: "[yuvDragSelect]", inputs: { yuvDragSelect: { classPropertyName: "yuvDragSelect", publicName: "yuvDragSelect", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dragSelectChange: "dragSelectChange", dragSelect: "dragSelect" }, host: { listeners: { "pointerdown": "onPointerDown($event)" } }, queries: [{ propertyName: "items", predicate: DragSelectItemDirective, isSignal: true }], ngImport: i0 });
626
+ }
627
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DragSelectDirective, decorators: [{
628
+ type: Directive,
629
+ args: [{
630
+ selector: '[yuvDragSelect]',
631
+ host: {
632
+ '(pointerdown)': 'onPointerDown($event)'
633
+ }
634
+ }]
635
+ }], propDecorators: { items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DragSelectItemDirective), { isSignal: true }] }], yuvDragSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "yuvDragSelect", required: false }] }], dragSelectChange: [{ type: i0.Output, args: ["dragSelectChange"] }], dragSelect: [{ type: i0.Output, args: ["dragSelect"] }] } });
636
+ class DragSelectItemDirective {
637
+ #elRef = inject(ElementRef);
638
+ el = this.#elRef.nativeElement;
639
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DragSelectItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
640
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: DragSelectItemDirective, isStandalone: true, selector: "[yuvDragSelectItem]", ngImport: i0 });
641
+ }
642
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DragSelectItemDirective, decorators: [{
643
+ type: Directive,
644
+ args: [{
645
+ selector: '[yuvDragSelectItem]',
646
+ standalone: true
647
+ }]
648
+ }] });
649
+
650
+ class FileDropService {
651
+ activeDropZone = signal(null, ...(ngDevMode ? [{ debugName: "activeDropZone" }] : /* istanbul ignore next */ []));
652
+ fileOver(id, active) {
653
+ this.activeDropZone.set(active ? id : null);
654
+ }
655
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FileDropService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
656
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FileDropService, providedIn: 'root' });
657
+ }
658
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FileDropService, decorators: [{
659
+ type: Injectable,
660
+ args: [{
661
+ providedIn: 'root'
662
+ }]
663
+ }] });
664
+
665
+ class FileDropZoneDirective {
666
+ #elRef = inject(ElementRef);
667
+ #fileDropService = inject(FileDropService);
668
+ #coverElement;
669
+ // ID to track the element
670
+ #id = crypto.randomUUID();
671
+ active = computed(() => {
672
+ const fileOver = this.#fileDropService.activeDropZone() === this.#id;
673
+ this.fileDropOver.emit(fileOver);
674
+ return fileOver;
675
+ }, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
676
+ #activeEffect = effect(() => {
677
+ this.#toggleCover(this.active());
678
+ }, ...(ngDevMode ? [{ debugName: "#activeEffect" }] : /* istanbul ignore next */ []));
679
+ #defaultDragOverCoverStyles = {
680
+ // outline: '2px dashed var(--ymt-primary)',
681
+ display: 'flex',
682
+ transition: 'background-color .3s ease-in-out',
683
+ 'z-index': '500',
684
+ 'align-items': 'center',
685
+ 'justify-content': 'center',
686
+ 'outline-offset': '-2px',
687
+ 'background-color': 'rgb(from var(--ymt-primary) r g b / .5)'
688
+ };
689
+ #defaultDragOverCoverLabelStyles = {
690
+ outline: '1px solid var(--ymt-primary)',
691
+ padding: 'var(--ymt-spacing-m, 16px)',
692
+ 'background-color': 'rgb(from var(--ymt-primary) r g b / 0.9)',
693
+ color: 'var(--ymt-on-primary)'
694
+ };
695
+ yuvFileDropZone = input(...(ngDevMode ? [undefined, { debugName: "yuvFileDropZone" }] : /* istanbul ignore next */ []));
696
+ fileDropDisabled = input(false, ...(ngDevMode ? [{ debugName: "fileDropDisabled" }] : /* istanbul ignore next */ []));
697
+ fileDrop = output();
698
+ fileDropOver = output();
699
+ onDrop(event) {
700
+ if (this.fileDropDisabled())
701
+ return;
702
+ event.preventDefault();
703
+ event.stopPropagation();
704
+ this.#fileDropService.fileOver(this.#id, false);
705
+ const dataTransfer = event.dataTransfer;
706
+ if (dataTransfer) {
707
+ if (dataTransfer?.items) {
708
+ const files = [];
709
+ for (let i = 0; i < dataTransfer.items.length; i++) {
710
+ // If dropped items aren't files, reject them
711
+ if (dataTransfer.items[i].kind === 'file') {
712
+ const file = dataTransfer.items[i].getAsFile();
713
+ if (file)
714
+ files.push(file);
715
+ }
716
+ }
717
+ dataTransfer.items.clear();
718
+ this.fileDrop.emit(files);
719
+ }
720
+ else {
721
+ const files = dataTransfer.files;
722
+ dataTransfer.clearData();
723
+ this.fileDrop.emit(Array.from(files));
724
+ }
725
+ }
726
+ }
727
+ onDragOver(event) {
728
+ if (this.fileDropDisabled())
729
+ return;
730
+ event.stopPropagation();
731
+ event.preventDefault();
732
+ this.#fileDropService.fileOver(this.#id, true);
733
+ }
734
+ onDragLeave(event) {
735
+ if (this.fileDropDisabled())
736
+ return;
737
+ if (event.target.getAttribute('id') === this.#coverElement?.getAttribute('id')) {
738
+ this.#fileDropService.fileOver(this.#id, false);
739
+ }
740
+ }
741
+ onBodyDragOver(event) {
742
+ event.preventDefault();
743
+ event.stopPropagation();
744
+ }
745
+ onBodyDrop(event) {
746
+ event.preventDefault();
747
+ }
748
+ #toggleCover(active) {
749
+ const el = this.#elRef.nativeElement;
750
+ if (this.#coverElement) {
751
+ el.style.position = 'initial';
752
+ this.#coverElement.remove();
753
+ this.#coverElement = undefined;
754
+ }
755
+ if (active) {
756
+ el.style.position = 'relative';
757
+ this.#coverElement = this.#createCoverElement();
758
+ el.append(this.#coverElement);
759
+ }
760
+ }
761
+ #createCoverElement() {
762
+ const coverElement = document.createElement('div');
763
+ coverElement.classList.add('yuv-file-drop-zone-cover');
764
+ coverElement.setAttribute('id', this.#id);
765
+ coverElement.style.position = 'absolute';
766
+ coverElement.style.inset = '0';
767
+ const styles = this.yuvFileDropZone()?.coverStyles || this.#defaultDragOverCoverStyles;
768
+ Object.keys(styles).forEach((k) => {
769
+ coverElement.style[k] = styles[k];
770
+ });
771
+ const label = this.yuvFileDropZone()?.label;
772
+ if (label) {
773
+ const coverLabelElement = document.createElement('div');
774
+ coverElement.classList.add('yuv-file-drop-zone-label');
775
+ coverLabelElement.innerText = label;
776
+ const labelStyles = this.yuvFileDropZone()?.coverLabelStyles || this.#defaultDragOverCoverLabelStyles;
777
+ Object.keys(labelStyles).forEach((k) => {
778
+ coverLabelElement.style[k] = labelStyles[k];
779
+ });
780
+ coverLabelElement.style.pointerEvents = 'none';
781
+ coverElement.append(coverLabelElement);
782
+ }
783
+ return coverElement;
784
+ }
785
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FileDropZoneDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
786
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: FileDropZoneDirective, isStandalone: true, selector: "[yuvFileDropZone]", inputs: { yuvFileDropZone: { classPropertyName: "yuvFileDropZone", publicName: "yuvFileDropZone", isSignal: true, isRequired: false, transformFunction: null }, fileDropDisabled: { classPropertyName: "fileDropDisabled", publicName: "fileDropDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileDrop: "fileDrop", fileDropOver: "fileDropOver" }, host: { listeners: { "drop": "onDrop($event)", "dragover": "onDragOver($event)", "dragleave": "onDragLeave($event)", "body:dragover": "onBodyDragOver($event)", "body:drop": "onBodyDrop($event)" } }, ngImport: i0 });
787
+ }
788
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FileDropZoneDirective, decorators: [{
789
+ type: Directive,
790
+ args: [{
791
+ selector: '[yuvFileDropZone]',
792
+ host: {
793
+ '(drop)': 'onDrop($event)',
794
+ '(dragover)': 'onDragOver($event)',
795
+ '(dragleave)': 'onDragLeave($event)',
796
+ '(body:dragover)': 'onBodyDragOver($event)',
797
+ '(body:drop)': 'onBodyDrop($event)'
798
+ }
799
+ }]
800
+ }], propDecorators: { yuvFileDropZone: [{ type: i0.Input, args: [{ isSignal: true, alias: "yuvFileDropZone", required: false }] }], fileDropDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileDropDisabled", required: false }] }], fileDrop: [{ type: i0.Output, args: ["fileDrop"] }], fileDropOver: [{ type: i0.Output, args: ["fileDropOver"] }] } });
801
+
802
+ /**
803
+ * Directive keeping track of the focus beeing within a component. Once the component or
804
+ * any of its child components gain focus, a class of `focusWithin` will be set on the
805
+ * host component in order to allow styling it while beeing focused. Furthermore you can
806
+ * register callbacks once the component gets or looses focus.
807
+ *
808
+ * @example
809
+ * // just set the css class
810
+ * <some-component yuvFocusWithin></some-component>
811
+ * // set the css class and listen to focus changes
812
+ * <some-component (yuvFocusWithin)="onFocusEnter()" (yuvFocusWithinBlur)="onFocusLeave()"></some-component>
813
+ */
814
+ class FocusWithinDirective {
815
+ elRef;
816
+ eventCount = 0;
817
+ hasFocusWithin = false;
818
+ onFocusIn(evt) {
819
+ const hadFocusWithin = this.hasFocusWithin;
820
+ this.hasFocusWithin = this.matchesFocusWithin();
821
+ if (!hadFocusWithin && this.hasFocusWithin) {
822
+ this.yuvFocusWithin.emit();
823
+ }
824
+ }
825
+ onFocusOut(evt) {
826
+ const hadFocusWithin = this.hasFocusWithin;
827
+ this.hasFocusWithin = this.matchesFocusWithin();
828
+ if (hadFocusWithin && !this.hasFocusWithin) {
829
+ this.yuvFocusWithinBlur.emit();
830
+ }
831
+ }
832
+ /**
833
+ * Emitted once the component or any of its child components gains focus.
834
+ */
835
+ yuvFocusWithin = output();
836
+ /**
837
+ * Emitted once the component (incl. any of its child components) looses focus.
838
+ */
839
+ yuvFocusWithinBlur = output();
840
+ /**
841
+ * @ignore
842
+ */
843
+ constructor(elRef) {
844
+ this.elRef = elRef;
845
+ }
846
+ // Determine if the given node matches the given selector.
847
+ // @see: https://www.bennadel.com/blog/3476-checking-to-see-if-an-element-has-a-css-pseudo-class-in-javascript.htm
848
+ matchesFocusWithin() {
849
+ const node = this.elRef.nativeElement;
850
+ const nativeMatches = node.matches || node.msMatchesSelector;
851
+ try {
852
+ return nativeMatches.call(node, ':focus-within');
853
+ }
854
+ catch (error) {
855
+ return false;
856
+ }
857
+ }
858
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FocusWithinDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
859
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: FocusWithinDirective, isStandalone: true, selector: "[yuvFocusWithin]", outputs: { yuvFocusWithin: "yuvFocusWithin", yuvFocusWithinBlur: "yuvFocusWithinBlur" }, host: { listeners: { "focusin": "onFocusIn($event)", "focusout": "onFocusOut($event)" }, properties: { "class.focusWithin": "hasFocusWithin" } }, ngImport: i0 });
860
+ }
861
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FocusWithinDirective, decorators: [{
862
+ type: Directive,
863
+ args: [{
864
+ selector: '[yuvFocusWithin]',
865
+ host: {
866
+ '[class.focusWithin]': 'hasFocusWithin',
867
+ '(focusin)': 'onFocusIn($event)',
868
+ '(focusout)': 'onFocusOut($event)'
869
+ }
870
+ }]
871
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { yuvFocusWithin: [{ type: i0.Output, args: ["yuvFocusWithin"] }], yuvFocusWithinBlur: [{ type: i0.Output, args: ["yuvFocusWithinBlur"] }] } });
872
+
873
+ /**
874
+ * Directive for applying light dismiss actions. Adding this directive will trigger
875
+ * the given function when the user clicks outside the component or hits escape.
876
+ *
877
+ * ```ts
878
+ * <div class="notifications" (yuvLightDismiss)="close()">
879
+ * ...
880
+ * </div>
881
+ * ```
882
+ */
883
+ class LightDismissDirective {
884
+ elRef = inject(ElementRef);
885
+ onKeydownHandler(event) {
886
+ if (!event.defaultPrevented) {
887
+ this.yuvLightDismiss.emit();
888
+ }
889
+ }
890
+ onMousedown(event) {
891
+ if (!this.elRef.nativeElement.contains(event.target)) {
892
+ this.yuvLightDismiss.emit();
893
+ }
894
+ }
895
+ yuvLightDismiss = output();
896
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LightDismissDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
897
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: LightDismissDirective, isStandalone: true, selector: "[yuvLightDismiss]", outputs: { yuvLightDismiss: "yuvLightDismiss" }, host: { listeners: { "document:keydown.escape": "onKeydownHandler($event)", "document:mousedown": "onMousedown($event)" } }, ngImport: i0 });
898
+ }
899
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LightDismissDirective, decorators: [{
900
+ type: Directive,
901
+ args: [{
902
+ selector: '[yuvLightDismiss]',
903
+ host: {
904
+ '(document:keydown.escape)': 'onKeydownHandler($event)',
905
+ '(document:mousedown)': 'onMousedown($event)'
906
+ }
907
+ }]
908
+ }], propDecorators: { yuvLightDismiss: [{ type: i0.Output, args: ["yuvLightDismiss"] }] } });
909
+
910
+ class LongPressDirective {
911
+ elRef = inject(ElementRef);
912
+ threshold = 500;
913
+ yuvLongPress = input({
914
+ enabled: false
915
+ }, ...(ngDevMode ? [{ debugName: "yuvLongPress" }] : /* istanbul ignore next */ []));
916
+ longpress = output();
917
+ constructor() {
918
+ const mousedown = fromEvent(this.elRef.nativeElement, 'mousedown').pipe(takeUntilDestroyed(), filter((event) => event.button == 0), // Only allow left button (Primary button)
919
+ map(() => true) // turn on threshold counter
920
+ );
921
+ const touchstart = fromEvent(this.elRef.nativeElement, 'touchstart').pipe(takeUntilDestroyed(), map(() => true));
922
+ const touchEnd = fromEvent(this.elRef.nativeElement, 'touchend').pipe(takeUntilDestroyed(), map(() => false));
923
+ const mouseup = fromEvent(window, 'mouseup').pipe(takeUntilDestroyed(), filter((event) => event.button == 0), // Only allow left button (Primary button)
924
+ map(() => false) // reset threshold counter
925
+ );
926
+ merge(mousedown, mouseup, touchstart, touchEnd)
927
+ .pipe(takeUntilDestroyed(), switchMap((state) => (state ? timer(this.threshold, 100) : of(null))), filter((value) => !!value))
928
+ .subscribe(() => this.longpress.emit());
929
+ }
930
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LongPressDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
931
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: LongPressDirective, isStandalone: true, selector: "[yuvLongPress]", inputs: { yuvLongPress: { classPropertyName: "yuvLongPress", publicName: "yuvLongPress", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { longpress: "longpress" }, ngImport: i0 });
932
+ }
933
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LongPressDirective, decorators: [{
934
+ type: Directive,
935
+ args: [{
936
+ selector: '[yuvLongPress]',
937
+ standalone: true
938
+ }]
939
+ }], ctorParameters: () => [], propDecorators: { yuvLongPress: [{ type: i0.Input, args: [{ isSignal: true, alias: "yuvLongPress", required: false }] }], longpress: [{ type: i0.Output, args: ["longpress"] }] } });
940
+
941
+ /* eslint-disable @typescript-eslint/no-unused-vars */
942
+ /* eslint-disable @typescript-eslint/no-empty-function */
943
+ // @see: https://netbasal.com/forwarding-form-controls-to-custom-control-components-in-angular-701e8406cc55
944
+ class NoopValueAccessorDirective {
945
+ writeValue(obj) { }
946
+ registerOnChange(fn) { }
947
+ registerOnTouched(fn) { }
948
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NoopValueAccessorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
949
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: NoopValueAccessorDirective, isStandalone: true, providers: [
950
+ {
951
+ provide: NG_VALUE_ACCESSOR,
952
+ multi: true,
953
+ useExisting: forwardRef(() => NoopValueAccessorDirective)
954
+ }
955
+ ], ngImport: i0 });
956
+ }
957
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NoopValueAccessorDirective, decorators: [{
958
+ type: Directive,
959
+ args: [{
960
+ standalone: true,
961
+ providers: [
962
+ {
963
+ provide: NG_VALUE_ACCESSOR,
964
+ multi: true,
965
+ useExisting: forwardRef(() => NoopValueAccessorDirective)
966
+ }
967
+ ]
968
+ }]
969
+ }] });
970
+ function injectNgControl(cva) {
971
+ const ngControl = inject(NgControl, { self: true, optional: true });
972
+ if (!ngControl)
973
+ return null;
974
+ if (ngControl instanceof FormControlDirective || ngControl instanceof FormControlName || ngControl instanceof NgModel) {
975
+ if (cva)
976
+ ngControl.valueAccessor = cva;
977
+ return ngControl;
978
+ }
979
+ throw new Error('...');
980
+ }
981
+
982
+ const DEFAULT_SCROLL_AMOUNT = 150;
983
+ const THRESHOLD = 1;
984
+ /**
985
+ * Directive that adds left/right scroll buttons around the host element when its content overflows horizontally.
986
+ * The directive restructures the DOM by wrapping the host inside a scroll container with navigation buttons.
987
+ *
988
+ * @example
989
+ * <mat-chip-grid yuvScrollButtons>...</mat-chip-grid>
990
+ *
991
+ * @example
992
+ * <div yuvScrollButtons [yuvScrollButtonsAmount]="200">...</div>
993
+ */
994
+ class ScrollButtonsDirective {
995
+ #renderer = inject(Renderer2);
996
+ #elRef = inject(ElementRef);
997
+ translate = inject(TranslateService);
998
+ #destroyRef = inject(DestroyRef);
999
+ /** How many pixels to scroll per button click. */
1000
+ // eslint-disable-next-line @typescript-eslint/member-ordering
1001
+ yuvScrollButtonsAmount = input(DEFAULT_SCROLL_AMOUNT, ...(ngDevMode ? [{ debugName: "yuvScrollButtonsAmount" }] : /* istanbul ignore next */ []));
1002
+ #leftBtn;
1003
+ #rightBtn;
1004
+ #scrollContent;
1005
+ constructor() {
1006
+ afterNextRender(() => this.#init());
1007
+ }
1008
+ #init() {
1009
+ const host = this.#elRef.nativeElement;
1010
+ const parent = host.parentNode;
1011
+ const renderer = this.#renderer;
1012
+ // Create wrapper
1013
+ const wrapper = renderer.createElement('div');
1014
+ this.#applyStyles(wrapper, {
1015
+ display: 'flex',
1016
+ 'align-items': 'center',
1017
+ flex: '1',
1018
+ 'min-width': '0'
1019
+ });
1020
+ // Create scroll content container
1021
+ this.#scrollContent = renderer.createElement('div');
1022
+ this.#applyStyles(this.#scrollContent, {
1023
+ flex: '1',
1024
+ 'overflow-x': 'auto',
1025
+ 'scrollbar-width': 'none',
1026
+ 'min-width': '0'
1027
+ });
1028
+ // Create buttons
1029
+ this.#leftBtn = this.#createButton('chevron_left');
1030
+ this.#rightBtn = this.#createButton('chevron_right');
1031
+ // Restructure DOM: replace host with wrapper, move host into scroll content
1032
+ renderer.insertBefore(parent, wrapper, host);
1033
+ renderer.appendChild(this.#scrollContent, host);
1034
+ renderer.appendChild(wrapper, this.#leftBtn);
1035
+ renderer.appendChild(wrapper, this.#scrollContent);
1036
+ renderer.appendChild(wrapper, this.#rightBtn);
1037
+ // Apply chip-specific overrides
1038
+ this.#applyChipOverrides(host);
1039
+ // Hide buttons initially
1040
+ this.#setButtonVisible(this.#leftBtn, false);
1041
+ this.#setButtonVisible(this.#rightBtn, false);
1042
+ // Set up listeners
1043
+ this.#scrollContent.addEventListener('scroll', () => this.#checkOverflow(), { passive: true });
1044
+ const resizeObserver = new ResizeObserver(() => this.#checkOverflow());
1045
+ resizeObserver.observe(this.#scrollContent);
1046
+ const mutationObserver = new MutationObserver(() => this.#checkOverflow());
1047
+ mutationObserver.observe(this.#scrollContent, { childList: true, subtree: true });
1048
+ this.#leftBtn.addEventListener('click', () => {
1049
+ this.#scrollContent.scrollBy({ left: -this.yuvScrollButtonsAmount(), behavior: 'smooth' });
1050
+ });
1051
+ this.#rightBtn.addEventListener('click', () => {
1052
+ this.#scrollContent.scrollBy({ left: this.yuvScrollButtonsAmount(), behavior: 'smooth' });
1053
+ });
1054
+ this.#destroyRef.onDestroy(() => {
1055
+ resizeObserver.disconnect();
1056
+ mutationObserver.disconnect();
1057
+ });
1058
+ this.#checkOverflow();
1059
+ }
1060
+ #createButton(icon) {
1061
+ const btn = this.#renderer.createElement('button');
1062
+ btn.setAttribute('aria-label', icon === 'chevron_left'
1063
+ ? this.translate.instant('yuv.object-form-element.scroll.button.left')
1064
+ : this.translate.instant('yuv.object-form-element.scroll.button.right'));
1065
+ this.#applyStyles(btn, {
1066
+ 'flex-shrink': '0',
1067
+ display: 'inline-flex',
1068
+ 'align-items': 'center',
1069
+ 'justify-content': 'center',
1070
+ background: 'none',
1071
+ border: 'none',
1072
+ cursor: 'pointer',
1073
+ padding: '0',
1074
+ color: 'var(--ymt-text-color-subtle)',
1075
+ width: 'var(--ymt-sizing-m)',
1076
+ height: 'var(--ymt-sizing-m)',
1077
+ backgroundColor: 'var(--ymt-surface-hover)'
1078
+ });
1079
+ const iconEl = this.#renderer.createElement('span');
1080
+ iconEl.classList.add('material-symbols-sharp');
1081
+ this.#applyStyles(iconEl, {
1082
+ 'font-size': '1.25rem', //calc(var(--ymt-sizing-s) * 0.8)
1083
+ width: 'var(--ymt-sizing-s)',
1084
+ height: 'var(--ymt-sizing-s)'
1085
+ });
1086
+ iconEl.textContent = icon;
1087
+ this.#renderer.appendChild(btn, iconEl);
1088
+ const highlight = () => {
1089
+ btn.style.color = 'var(--ymt-text-color)';
1090
+ };
1091
+ const reset = () => {
1092
+ btn.style.color = 'var(--ymt-text-color-subtle)';
1093
+ };
1094
+ btn.addEventListener('mouseenter', highlight);
1095
+ btn.addEventListener('mouseleave', reset);
1096
+ btn.addEventListener('focusin', highlight);
1097
+ btn.addEventListener('focusout', reset);
1098
+ // Focus-visible outline via CSS class
1099
+ btn.addEventListener('focus', () => {
1100
+ if (btn.matches(':focus-visible')) {
1101
+ btn.style.outline = '2px solid var(--ymt-primary-color)';
1102
+ btn.style.outlineOffset = '-2px';
1103
+ }
1104
+ });
1105
+ btn.addEventListener('blur', () => {
1106
+ btn.style.outline = '';
1107
+ btn.style.outlineOffset = '';
1108
+ });
1109
+ return btn;
1110
+ }
1111
+ #applyChipOverrides(host) {
1112
+ const chipSet = host.querySelector('.mdc-evolution-chip-set__chips');
1113
+ if (chipSet) {
1114
+ chipSet.style.flexWrap = 'nowrap';
1115
+ }
1116
+ // Use MutationObserver to apply overrides to chip rows as they appear
1117
+ const applyRowOverrides = () => {
1118
+ host.querySelectorAll('mat-chip-row, .mat-mdc-chip-row').forEach((row) => {
1119
+ const rowElement = row;
1120
+ rowElement.style.flexShrink = '0';
1121
+ rowElement.style.maxWidth = 'none';
1122
+ const primaryCell = rowElement.querySelector('.mdc-evolution-chip__cell--primary');
1123
+ if (primaryCell) {
1124
+ primaryCell.style.maxWidth = 'none';
1125
+ }
1126
+ rowElement
1127
+ .querySelectorAll('.mdc-evolution-chip__text-label, .mat-mdc-chip-action-label')
1128
+ .forEach((label) => {
1129
+ label.style.whiteSpace = 'nowrap';
1130
+ });
1131
+ });
1132
+ };
1133
+ applyRowOverrides();
1134
+ const observer = new MutationObserver(() => applyRowOverrides());
1135
+ observer.observe(host, { childList: true, subtree: true });
1136
+ this.#destroyRef.onDestroy(() => observer.disconnect());
1137
+ }
1138
+ #setButtonVisible(btn, visible) {
1139
+ btn.style.display = visible ? 'inline-flex' : 'none';
1140
+ }
1141
+ #checkOverflow() {
1142
+ const scrollElement = this.#scrollContent;
1143
+ this.#setButtonVisible(this.#leftBtn, scrollElement.scrollLeft > THRESHOLD);
1144
+ this.#setButtonVisible(this.#rightBtn, scrollElement.scrollLeft + scrollElement.clientWidth < scrollElement.scrollWidth - THRESHOLD);
1145
+ }
1146
+ #applyStyles(element, styles) {
1147
+ Object.entries(styles).forEach(([prop, value]) => {
1148
+ this.#renderer.setStyle(element, prop, value);
1149
+ });
1150
+ }
1151
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ScrollButtonsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1152
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: ScrollButtonsDirective, isStandalone: true, selector: "[yuvScrollButtons]", inputs: { yuvScrollButtonsAmount: { classPropertyName: "yuvScrollButtonsAmount", publicName: "yuvScrollButtonsAmount", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
1153
+ }
1154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ScrollButtonsDirective, decorators: [{
1155
+ type: Directive,
1156
+ args: [{
1157
+ selector: '[yuvScrollButtons]'
1158
+ }]
1159
+ }], ctorParameters: () => [], propDecorators: { yuvScrollButtonsAmount: [{ type: i0.Input, args: [{ isSignal: true, alias: "yuvScrollButtonsAmount", required: false }] }] } });
1160
+
1161
+ const directives = [
1162
+ BusyOverlayDirective,
1163
+ FocusWithinDirective,
1164
+ FileDropZoneDirective,
1165
+ ClickDoubleDirective,
1166
+ LightDismissDirective,
1167
+ ContainerSizeDirective,
1168
+ LongPressDirective,
1169
+ DragSelectDirective,
1170
+ DragScrollDirective,
1171
+ NoopValueAccessorDirective,
1172
+ AutofocusChildDirective,
1173
+ AutofocusDelayedDirective,
1174
+ ScrollButtonsDirective
1175
+ ];
1176
+ const components = [ConfirmComponent, ScrollButtonsComponent];
1177
+ class YuvCommonModule {
1178
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: YuvCommonModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1179
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: YuvCommonModule, imports: [BusyOverlayDirective,
1180
+ FocusWithinDirective,
1181
+ FileDropZoneDirective,
1182
+ ClickDoubleDirective,
1183
+ LightDismissDirective,
1184
+ ContainerSizeDirective,
1185
+ LongPressDirective,
1186
+ DragSelectDirective,
1187
+ DragScrollDirective,
1188
+ NoopValueAccessorDirective,
1189
+ AutofocusChildDirective,
1190
+ AutofocusDelayedDirective,
1191
+ ScrollButtonsDirective, ConfirmComponent, ScrollButtonsComponent], exports: [BusyOverlayDirective,
1192
+ FocusWithinDirective,
1193
+ FileDropZoneDirective,
1194
+ ClickDoubleDirective,
1195
+ LightDismissDirective,
1196
+ ContainerSizeDirective,
1197
+ LongPressDirective,
1198
+ DragSelectDirective,
1199
+ DragScrollDirective,
1200
+ NoopValueAccessorDirective,
1201
+ AutofocusChildDirective,
1202
+ AutofocusDelayedDirective,
1203
+ ScrollButtonsDirective, ConfirmComponent, ScrollButtonsComponent] });
1204
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: YuvCommonModule, imports: [components] });
1205
+ }
1206
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: YuvCommonModule, decorators: [{
1207
+ type: NgModule,
1208
+ args: [{
1209
+ imports: [...directives, ...components],
1210
+ exports: [...directives, ...components]
1211
+ }]
1212
+ }] });
1213
+
1214
+ function getFocusableChildren(element) {
1215
+ return [...Array.from(element.querySelectorAll('a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])'))].filter((el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
1216
+ }
1217
+ function getFirstFocusableChild(element) {
1218
+ return getFocusableChildren(element)[0];
1219
+ }
1220
+
1221
+ /**
1222
+ * Service to store and retrieve layout settings. Those
1223
+ * settings are stored on the users device because in
1224
+ * general layout settings like panel widths are highly
1225
+ * dependent on the current device.
1226
+ */
1227
+ class LayoutSettingsService {
1228
+ #STORAGE_PREFIX = 'yuv.layout:';
1229
+ /**
1230
+ * @deprecated Theme storage is now handled by `ThemeService`.
1231
+ */
1232
+ #MODE_STORAGE_KEY = 'app.layout.mode';
1233
+ DEFAULT_SPLIT_VIEW_GUTTER_SIZE = 16;
1234
+ /**
1235
+ * @deprecated Use `ThemeService.mode` instead.
1236
+ */
1237
+ themeMode = signal('system', ...(ngDevMode ? [{ debugName: "themeMode" }] : /* istanbul ignore next */ []));
1238
+ /**
1239
+ * @deprecated Use `ThemeService.mode` instead.
1240
+ */
1241
+ mode = this.themeMode.asReadonly();
1242
+ /**
1243
+ * @deprecated Theme initialization is now handled by `ThemeService` internally.
1244
+ */
1245
+ init() {
1246
+ // set configured mode
1247
+ const modeSettings = this.getSettings(this.#MODE_STORAGE_KEY);
1248
+ this.applyLayoutMode(modeSettings?.mode || undefined);
1249
+ }
1250
+ /**
1251
+ * @deprecated Use `ThemeService.toggleTheme()` instead.
1252
+ */
1253
+ setMode(mode) {
1254
+ this.saveSettings(this.#MODE_STORAGE_KEY, { mode });
1255
+ }
1256
+ saveSettings(key, settings) {
1257
+ if (typeof settings === 'object') {
1258
+ localStorage.setItem(this.#getKey(key), JSON.stringify(settings));
1259
+ return true;
1260
+ }
1261
+ else
1262
+ return false;
1263
+ }
1264
+ getSettings(key) {
1265
+ try {
1266
+ const v = localStorage.getItem(this.#getKey(key));
1267
+ return v ? JSON.parse(v) : undefined;
1268
+ }
1269
+ catch (e) {
1270
+ console.error('Error while parsing layout settings', e);
1271
+ return undefined;
1272
+ }
1273
+ }
1274
+ #getKey(key) {
1275
+ return `${this.#STORAGE_PREFIX}${key}`;
1276
+ }
1277
+ /**
1278
+ * Clears all layout settings.
1279
+ */
1280
+ clearSettings() {
1281
+ Object.keys(localStorage).forEach((key) => {
1282
+ if (key.startsWith(this.#STORAGE_PREFIX)) {
1283
+ localStorage.removeItem(key);
1284
+ }
1285
+ });
1286
+ }
1287
+ /**
1288
+ * @deprecated Use `ThemeService.toggleTheme()` instead.
1289
+ */
1290
+ applyLayoutMode(mode) {
1291
+ const body = document.getElementsByTagName('body')[0];
1292
+ body.style.colorScheme = mode === 'dark' || mode === 'light' ? mode : 'inherit';
1293
+ this.themeMode.set(mode || 'system');
1294
+ }
1295
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutSettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1296
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutSettingsService, providedIn: 'root' });
1297
+ }
1298
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutSettingsService, decorators: [{
1299
+ type: Injectable,
1300
+ args: [{
1301
+ providedIn: 'root'
1302
+ }]
1303
+ }] });
1304
+
1305
+ /**
1306
+ * Providing an error message when a translation string for an object wasn't found
1307
+ */
1308
+ class FormTranslateService {
1309
+ translate = inject(TranslateService);
1310
+ /**
1311
+ * Set the error label if a translation for this string wasn't provided
1312
+ * @param error - error message about a missed translation key
1313
+ * @param params
1314
+ */
1315
+ getErrorLabel(error, params) {
1316
+ switch (error) {
1317
+ case 'daterange':
1318
+ return this.translate.instant('yuv.object-form-element.error.daterange.invalid', params);
1319
+ case 'daterangeorder':
1320
+ return this.translate.instant('yuv.object-form-element.error.daterangeorder.invalid', params);
1321
+ case 'numberrange':
1322
+ return this.translate.instant('yuv.object-form-element.error.numberrange.invalid', params);
1323
+ case 'numberrangeorder':
1324
+ return this.translate.instant('yuv.object-form-element.error.numberrangeorder.invalid', params);
1325
+ case 'number':
1326
+ return this.translate.instant('yuv.object-form-element.error.number', params);
1327
+ case 'precision':
1328
+ return this.translate.instant('yuv.object-form-element.error.number.precision', params);
1329
+ case 'scale':
1330
+ return this.translate.instant('yuv.object-form-element.error.number.scale', params);
1331
+ case 'regex':
1332
+ return this.translate.instant('yuv.object-form-element.error.string.regex.nomatch', params);
1333
+ case 'pattern':
1334
+ return this.translate.instant('yuv.object-form-element.error.string.regex.nomatch', params);
1335
+ case 'classificationemail':
1336
+ return this.translate.instant('yuv.object-form-element.error.string.classification.email', params);
1337
+ case 'classificationphone':
1338
+ return this.translate.instant('yuv.object-form-element.error.string.classification.phone', params);
1339
+ case 'classificationurl':
1340
+ return this.translate.instant('yuv.object-form-element.error.string.classification.url', params);
1341
+ case 'onlyWhitespaces':
1342
+ return this.translate.instant('yuv.object-form-element.error.string.whitespaces', params);
1343
+ case 'datecontrol':
1344
+ return this.translate.instant('yuv.object-form-element.error.date.invalid', params);
1345
+ case 'required':
1346
+ return this.translate.instant('yuv.object-form-element.error.required', params);
1347
+ case 'maxlength':
1348
+ return this.translate.instant('yuv.object-form-element.error.maxlength', params);
1349
+ case 'minlength':
1350
+ return this.translate.instant('yuv.object-form-element.error.minlength', params);
1351
+ case 'minmax':
1352
+ return this.translate.instant('yuv.object-form-element.error.minmax', params);
1353
+ case 'minvalue':
1354
+ return this.translate.instant('yuv.object-form-element.error.minvalue', params);
1355
+ case 'maxvalue':
1356
+ return this.translate.instant('yuv.object-form-element.error.maxvalue', params);
1357
+ default:
1358
+ return this.translate.instant(error, params);
1359
+ }
1360
+ }
1361
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormTranslateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1362
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormTranslateService, providedIn: 'root' });
1363
+ }
1364
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormTranslateService, decorators: [{
1365
+ type: Injectable,
1366
+ args: [{
1367
+ providedIn: 'root'
1368
+ }]
1369
+ }] });
1370
+
1371
+ var ThemeStorageKeys;
1372
+ (function (ThemeStorageKeys) {
1373
+ ThemeStorageKeys["THEME_MODE"] = "yuv.theme:app.theme.mode";
1374
+ ThemeStorageKeys["CUSTOM_THEME"] = "yuv.theme:app.theme.custom";
1375
+ })(ThemeStorageKeys || (ThemeStorageKeys = {}));
1376
+ const DEFAULT_THEME_KEY = 'yuv-default';
1377
+ const DEFAULT_THEME = {
1378
+ key: DEFAULT_THEME_KEY,
1379
+ label: 'yuv.theme.default.label',
1380
+ description: 'yuv.theme.default.description',
1381
+ hasLightTheme: true,
1382
+ hasDarkTheme: true
1383
+ };
1384
+ const HIGH_CONTRAST_THEME_KEY = 'yuv-high-contrast';
1385
+ const HIGH_CONTRAST_THEME = {
1386
+ key: HIGH_CONTRAST_THEME_KEY,
1387
+ label: 'yuv.theme.highContrast.label',
1388
+ description: 'yuv.theme.highContrast.description',
1389
+ hasLightTheme: true,
1390
+ hasDarkTheme: true
1391
+ };
1392
+
1393
+ const YUV_CUSTOM_THEME = new InjectionToken('CUSTOM_THEME', { factory: () => [HIGH_CONTRAST_THEME] });
1394
+ const provideYuvCustomTheme = (customTheme) => makeEnvironmentProviders([{ provide: YUV_CUSTOM_THEME, useValue: [HIGH_CONTRAST_THEME, ...customTheme] }]);
1395
+
1396
+ class ThemeService {
1397
+ #rendererFactory = inject(RendererFactory2);
1398
+ #customThemesToken = inject(YUV_CUSTOM_THEME, { optional: true });
1399
+ #document = inject(DOCUMENT);
1400
+ #storage = inject(AppCacheService);
1401
+ #MODE_STORAGE_KEY = ThemeStorageKeys.THEME_MODE;
1402
+ #CUSTOM_THEME_STORAGE_KEY = ThemeStorageKeys.CUSTOM_THEME;
1403
+ #renderer = this.#rendererFactory.createRenderer(null, null);
1404
+ #mode = signal('light', ...(ngDevMode ? [{ debugName: "#mode" }] : /* istanbul ignore next */ []));
1405
+ mode = this.#mode.asReadonly();
1406
+ customTheme = signal(DEFAULT_THEME, ...(ngDevMode ? [{ debugName: "customTheme" }] : /* istanbul ignore next */ []));
1407
+ customThemes = computed(() => {
1408
+ const customThemesToken = this.#customThemesToken || [];
1409
+ return customThemesToken.map((theme) => {
1410
+ const { key, label, description, hasLightTheme, hasDarkTheme } = theme;
1411
+ return {
1412
+ key,
1413
+ label,
1414
+ description,
1415
+ hasLightTheme: hasLightTheme ?? true,
1416
+ hasDarkTheme: hasDarkTheme ?? false
1417
+ };
1418
+ });
1419
+ }, ...(ngDevMode ? [{ debugName: "customThemes" }] : /* istanbul ignore next */ []));
1420
+ #currentTheme = signal(DEFAULT_THEME_KEY, ...(ngDevMode ? [{ debugName: "#currentTheme" }] : /* istanbul ignore next */ []));
1421
+ currentTheme = this.#currentTheme.asReadonly();
1422
+ #disableMode = signal(false, ...(ngDevMode ? [{ debugName: "#disableMode" }] : /* istanbul ignore next */ []));
1423
+ disableMode = this.#disableMode.asReadonly();
1424
+ // Effect to apply classes and save to localStorage
1425
+ #setThemeModeEffect = effect(() => {
1426
+ const currentTheme = this.#mode();
1427
+ const body = this.#document.getElementsByTagName('body')[0];
1428
+ this.setMode(currentTheme);
1429
+ // Theme class
1430
+ if (currentTheme === 'dark') {
1431
+ this.#renderer.setAttribute(body, 'style', 'color-scheme: dark');
1432
+ }
1433
+ else if (currentTheme === 'light') {
1434
+ this.#renderer.setAttribute(body, 'style', 'color-scheme: light');
1435
+ }
1436
+ else {
1437
+ this.#renderer.removeAttribute(body, 'style');
1438
+ }
1439
+ }, ...(ngDevMode ? [{ debugName: "#setThemeModeEffect" }] : /* istanbul ignore next */ []));
1440
+ constructor() {
1441
+ this.#initializeModes();
1442
+ }
1443
+ #saveSettings(key, settings) {
1444
+ if (typeof settings === 'object') {
1445
+ this.#storage.setItem(key, JSON.stringify(settings)).subscribe();
1446
+ return true;
1447
+ }
1448
+ else
1449
+ return false;
1450
+ }
1451
+ #deleteSettings(key) {
1452
+ this.#storage.removeItem(key).subscribe();
1453
+ return true;
1454
+ }
1455
+ #checkPrefersContrast() {
1456
+ const prefersHighContrast = window.matchMedia('(prefers-contrast: more)').matches;
1457
+ }
1458
+ #initializeModes() {
1459
+ // Theme initialization
1460
+ forkJoin([this.#storage.getItem(this.#MODE_STORAGE_KEY), this.#storage.getItem(this.#CUSTOM_THEME_STORAGE_KEY)])
1461
+ .pipe(map$1(([savedTheme, savedCustomTheme]) => {
1462
+ if (savedTheme) {
1463
+ try {
1464
+ this.#mode.set(JSON.parse(savedTheme).mode);
1465
+ }
1466
+ catch (error) {
1467
+ this.#mode.set(this.#mode());
1468
+ }
1469
+ }
1470
+ else {
1471
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
1472
+ this.#mode.set(prefersDark ? 'dark' : 'light');
1473
+ }
1474
+ // Contrast initialization
1475
+ this.#checkPrefersContrast();
1476
+ // Custom Theme initialization
1477
+ if (savedCustomTheme) {
1478
+ try {
1479
+ const customThemeKey = JSON.parse(savedCustomTheme).customTheme;
1480
+ this.setCustomTheme(customThemeKey);
1481
+ }
1482
+ catch (error) {
1483
+ // ignore
1484
+ }
1485
+ }
1486
+ }))
1487
+ .subscribe();
1488
+ }
1489
+ setMode(mode) {
1490
+ this.#saveSettings(this.#MODE_STORAGE_KEY, { mode });
1491
+ }
1492
+ setCustomTheme(key) {
1493
+ // Reset contrast to system on custom theme change
1494
+ const previousTheme = this.customTheme();
1495
+ const body = this.#document.getElementsByTagName('body')[0];
1496
+ previousTheme && previousTheme.key && this.#renderer.removeClass(body, previousTheme.key);
1497
+ const selectedTheme = this.customThemes().find((theme) => theme.key === key);
1498
+ if (selectedTheme) {
1499
+ this.customTheme.set(selectedTheme);
1500
+ this.#currentTheme.set(selectedTheme.key);
1501
+ selectedTheme.key && this.#renderer.addClass(body, selectedTheme.key);
1502
+ // Check if custom theme has light or dark mode
1503
+ if ((selectedTheme.hasDarkTheme && selectedTheme.hasLightTheme) || (!selectedTheme.hasDarkTheme && !selectedTheme.hasLightTheme)) {
1504
+ // If both, do nothing further with this (enable group)
1505
+ this.#disableMode.set(false);
1506
+ }
1507
+ else {
1508
+ // Else disable mode button group and set the corresponding mode active
1509
+ if (selectedTheme.hasDarkTheme) {
1510
+ this.#mode.set('dark');
1511
+ }
1512
+ else if (selectedTheme.hasLightTheme) {
1513
+ this.#mode.set('light');
1514
+ }
1515
+ this.#disableMode.set(true);
1516
+ }
1517
+ this.#saveSettings(this.#CUSTOM_THEME_STORAGE_KEY, { customTheme: key });
1518
+ }
1519
+ else {
1520
+ if (key === DEFAULT_THEME_KEY) {
1521
+ this.#disableMode.set(false);
1522
+ this.#renderer.removeClass(body, HIGH_CONTRAST_THEME_KEY);
1523
+ this.#deleteSettings(this.#CUSTOM_THEME_STORAGE_KEY);
1524
+ }
1525
+ // Default theme or no theme found
1526
+ }
1527
+ }
1528
+ toggleTheme(theme) {
1529
+ this.#mode.set(theme);
1530
+ }
1531
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1532
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ThemeService, providedIn: 'root' });
1533
+ }
1534
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ThemeService, decorators: [{
1535
+ type: Injectable,
1536
+ args: [{
1537
+ providedIn: 'root'
1538
+ }]
1539
+ }], ctorParameters: () => [] });
1540
+
1541
+ marker('yuv.theme.default.label');
1542
+ marker('yuv.theme.default.description');
1543
+ marker('yuv.theme.highContrast.label');
1544
+ marker('yuv.theme.highContrast.description');
1545
+
1546
+ // eslint-disable-next-line @angular-eslint/component-class-suffix
1547
+ class AbstractMatFormField {
1548
+ stateChanges = new Subject();
1549
+ elRef = inject(ElementRef);
1550
+ onFocusIn() {
1551
+ this.focused = true;
1552
+ }
1553
+ onFocusout() {
1554
+ this.focused = false;
1555
+ }
1556
+ #value = signal(null, ...(ngDevMode ? [{ debugName: "#value" }] : /* istanbul ignore next */ []));
1557
+ set value(v) {
1558
+ this.#value.set(v);
1559
+ this.empty = !v;
1560
+ this.stateChanges.next();
1561
+ }
1562
+ get value() {
1563
+ return this.#value();
1564
+ }
1565
+ #id = signal('', ...(ngDevMode ? [{ debugName: "#id" }] : /* istanbul ignore next */ []));
1566
+ set id(v) {
1567
+ this.#id.set(v);
1568
+ this.stateChanges.next();
1569
+ }
1570
+ get id() {
1571
+ return this.#id();
1572
+ }
1573
+ #placeholder = signal('', ...(ngDevMode ? [{ debugName: "#placeholder" }] : /* istanbul ignore next */ []));
1574
+ set placeholder(v) {
1575
+ this.#placeholder.set(v);
1576
+ this.stateChanges.next();
1577
+ }
1578
+ get placeholder() {
1579
+ return this.#placeholder();
1580
+ }
1581
+ ngControl = null;
1582
+ #focused = signal(false, ...(ngDevMode ? [{ debugName: "#focused" }] : /* istanbul ignore next */ []));
1583
+ set focused(f) {
1584
+ this.#focused.set(f);
1585
+ this.stateChanges.next();
1586
+ }
1587
+ get focused() {
1588
+ return this.#focused();
1589
+ }
1590
+ // #shouldLabelFloat = signal<boolean>(false);
1591
+ // set shouldLabelFloat(f: boolean) {
1592
+ // this.#shouldLabelFloat.set(f);
1593
+ // this.stateChanges.next();
1594
+ // }
1595
+ get shouldLabelFloat() {
1596
+ return this.focused || !this.empty || !!this.ngControl?.invalid;
1597
+ }
1598
+ #empty = signal(false, ...(ngDevMode ? [{ debugName: "#empty" }] : /* istanbul ignore next */ []));
1599
+ set empty(f) {
1600
+ this.#empty.set(f);
1601
+ this.stateChanges.next();
1602
+ }
1603
+ get empty() {
1604
+ return this.#empty();
1605
+ }
1606
+ #required = signal(false, ...(ngDevMode ? [{ debugName: "#required" }] : /* istanbul ignore next */ []));
1607
+ set required(f) {
1608
+ this.#required.set(coerceBooleanProperty(f));
1609
+ this.stateChanges.next();
1610
+ }
1611
+ get required() {
1612
+ return this.#required();
1613
+ }
1614
+ #disabled = signal(false, ...(ngDevMode ? [{ debugName: "#disabled" }] : /* istanbul ignore next */ []));
1615
+ set disabled(f) {
1616
+ this.#disabled.set(coerceBooleanProperty(f));
1617
+ this.stateChanges.next();
1618
+ }
1619
+ get disabled() {
1620
+ return this.#disabled();
1621
+ }
1622
+ focusHandled = signal(false, ...(ngDevMode ? [{ debugName: "focusHandled" }] : /* istanbul ignore next */ []));
1623
+ #errorState = signal(false, ...(ngDevMode ? [{ debugName: "#errorState" }] : /* istanbul ignore next */ []));
1624
+ set errorState(f) {
1625
+ this.#errorState.set(coerceBooleanProperty(f));
1626
+ this.stateChanges.next();
1627
+ }
1628
+ get errorState() {
1629
+ return this.#errorState();
1630
+ }
1631
+ controlType;
1632
+ autofilled;
1633
+ userAriaDescribedBy;
1634
+ disableAutomaticLabeling;
1635
+ setDescribedByIds(ids) {
1636
+ // this.describedBy = ids.join(' ');
1637
+ }
1638
+ onContainerClick(event) {
1639
+ if (this.focusHandled())
1640
+ return;
1641
+ const fe = getFocusableChildren(this.elRef.nativeElement);
1642
+ if (fe[0] && !fe.includes(event.target)) {
1643
+ fe[0].focus();
1644
+ }
1645
+ }
1646
+ onNgOnDestroy() {
1647
+ this.stateChanges.complete();
1648
+ }
1649
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AbstractMatFormField, deps: [], target: i0.ɵɵFactoryTarget.Component });
1650
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: AbstractMatFormField, isStandalone: true, selector: "ng-component", inputs: { placeholder: "placeholder", required: "required", disabled: "disabled" }, host: { listeners: { "focusin": "onFocusIn()", "focusout": "onFocusout()" } }, ngImport: i0, template: '', isInline: true });
1651
+ }
1652
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AbstractMatFormField, decorators: [{
1653
+ type: Component,
1654
+ args: [{
1655
+ template: '',
1656
+ host: {
1657
+ '(focusin)': 'onFocusIn()',
1658
+ '(focusout)': 'onFocusout()'
1659
+ }
1660
+ }]
1661
+ }], propDecorators: { placeholder: [{
1662
+ type: Input
1663
+ }], required: [{
1664
+ type: Input
1665
+ }], disabled: [{
1666
+ type: Input
1667
+ }] } });
1668
+
1669
+ var DialogSize;
1670
+ (function (DialogSize) {
1671
+ DialogSize["SMALL"] = "small";
1672
+ DialogSize["MEDIUM"] = "medium";
1673
+ DialogSize["LARGE"] = "large";
1674
+ DialogSize["EXTRA_LARGE"] = "extra-large";
1675
+ DialogSize["FULL_SCREEN"] = "full-screen";
1676
+ })(DialogSize || (DialogSize = {}));
1677
+
1678
+ class RetentionBadgeComponent {
1679
+ #retention = inject(RetentionService);
1680
+ dmsObject = input.required(...(ngDevMode ? [{ debugName: "dmsObject" }] : /* istanbul ignore next */ []));
1681
+ retentionData = computed(() => {
1682
+ return this.#retention.getRetentionState(this.dmsObject());
1683
+ }, ...(ngDevMode ? [{ debugName: "retentionData" }] : /* istanbul ignore next */ []));
1684
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RetentionBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1685
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: RetentionBadgeComponent, isStandalone: true, selector: "yuv-retention-badge", inputs: { dmsObject: { classPropertyName: "dmsObject", publicName: "dmsObject", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@let rd = retentionData();\n@if (rd.underRetention) {\n <div class=\"badge\">\n <span class=\"badge__icon\">\n <mat-icon class=\"ymt-icon--size-s\">lock_clock</mat-icon>\n </span>\n <span class=\"badge__label\">\n <span class=\"badge__label-truncated\">\n {{\n 'yuv.retention-badge.retain'\n | translate\n : {\n from: rd.start | localeDate: 'shortDate',\n until: rd.end | localeDate: 'shortDate'\n }\n }}\n </span>\n </span>\n </div>\n}\n", styles: [":host{display:contents}:host .badge{font:var(--ymt-font-body-subtle);color:var(--badge-retention-color, var(--ymt-on-surface));display:inline-flex;overflow:hidden;padding:var(--ymt-spacing-2xs) var(--ymt-spacing-s) var(--ymt-spacing-2xs) var(--ymt-spacing-xs);background-color:var(--badge-retention-background, var(--ymt-primary-container));border-radius:var(--ymt-corner-full);gap:var(--ymt-spacing-s)}:host .badge__icon{flex-shrink:0;color:var(--badge-retention-icon-color, var(--ymt-on-primary-container));display:flex;align-items:center;justify-content:center}:host .badge__label{color:var(--badge-retention-color, var(--ymt-on-surface));align-self:stretch;display:flex;align-items:center;justify-content:center;border-top-right-radius:var(--ymt-corner-full);border-bottom-right-radius:var(--ymt-corner-full);overflow:hidden}:host .badge__label-truncated{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;display:block}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: LocaleDatePipe, name: "localeDate" }] });
1686
+ }
1687
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RetentionBadgeComponent, decorators: [{
1688
+ type: Component,
1689
+ args: [{ selector: 'yuv-retention-badge', standalone: true, imports: [TranslatePipe, LocaleDatePipe, MatIconModule], template: "@let rd = retentionData();\n@if (rd.underRetention) {\n <div class=\"badge\">\n <span class=\"badge__icon\">\n <mat-icon class=\"ymt-icon--size-s\">lock_clock</mat-icon>\n </span>\n <span class=\"badge__label\">\n <span class=\"badge__label-truncated\">\n {{\n 'yuv.retention-badge.retain'\n | translate\n : {\n from: rd.start | localeDate: 'shortDate',\n until: rd.end | localeDate: 'shortDate'\n }\n }}\n </span>\n </span>\n </div>\n}\n", styles: [":host{display:contents}:host .badge{font:var(--ymt-font-body-subtle);color:var(--badge-retention-color, var(--ymt-on-surface));display:inline-flex;overflow:hidden;padding:var(--ymt-spacing-2xs) var(--ymt-spacing-s) var(--ymt-spacing-2xs) var(--ymt-spacing-xs);background-color:var(--badge-retention-background, var(--ymt-primary-container));border-radius:var(--ymt-corner-full);gap:var(--ymt-spacing-s)}:host .badge__icon{flex-shrink:0;color:var(--badge-retention-icon-color, var(--ymt-on-primary-container));display:flex;align-items:center;justify-content:center}:host .badge__label{color:var(--badge-retention-color, var(--ymt-on-surface));align-self:stretch;display:flex;align-items:center;justify-content:center;border-top-right-radius:var(--ymt-corner-full);border-bottom-right-radius:var(--ymt-corner-full);overflow:hidden}:host .badge__label-truncated{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;display:block}\n"] }]
1690
+ }], propDecorators: { dmsObject: [{ type: i0.Input, args: [{ isSignal: true, alias: "dmsObject", required: true }] }] } });
1691
+
1692
+ class ConfirmService {
1693
+ #dialog = inject(MatDialog);
1694
+ confirm(data) {
1695
+ return this.#dialog
1696
+ .open(ConfirmComponent, { data })
1697
+ .afterClosed()
1698
+ .pipe(map((result) => !!result));
1699
+ }
1700
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ConfirmService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1701
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ConfirmService, providedIn: 'root' });
1702
+ }
1703
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ConfirmService, decorators: [{
1704
+ type: Injectable,
1705
+ args: [{
1706
+ providedIn: 'root'
1707
+ }]
1708
+ }] });
1709
+
1710
+ class HaloFocusComponent {
1711
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HaloFocusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1712
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: HaloFocusComponent, isStandalone: true, selector: "yuv-halo-focus", ngImport: i0, template: "<p>halo-focus works!</p>\n", styles: [""] });
1713
+ }
1714
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HaloFocusComponent, decorators: [{
1715
+ type: Component,
1716
+ args: [{ selector: 'yuv-halo-focus', imports: [], template: "<p>halo-focus works!</p>\n" }]
1717
+ }] });
1718
+
1719
+ /**
1720
+ * Generated bundle index. Do not edit.
1721
+ */
1722
+
1723
+ export { AbstractMatFormField, AutofocusChildDirective, AutofocusDelayedDirective, BusyOverlayComponent, BusyOverlayDirective, ClickDoubleDirective, ConfirmComponent, ConfirmService, ContainerSizeDirective, DEFAULT_THEME, DEFAULT_THEME_KEY, DialogComponent, DialogSize, DragScrollDirective, DragSelectDirective, DragSelectItemDirective, FileDropZoneDirective, FocusWithinDirective, FormTranslateService, HIGH_CONTRAST_THEME, HIGH_CONTRAST_THEME_KEY, HaloFocusComponent, LayoutSettingsService, LightDismissDirective, LongPressDirective, NoopValueAccessorDirective, RetentionBadgeComponent, ScrollButtonsComponent, ScrollButtonsDirective, ThemeService, ThemeStorageKeys, YUV_CUSTOM_THEME, YuvCommonModule, getFirstFocusableChild, getFocusableChildren, injectNgControl, provideYuvCustomTheme };
1724
+ //# sourceMappingURL=yuuvis-client-components-common.mjs.map