ud-components 0.5.14 → 0.5.17

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.
@@ -1,9 +1,9 @@
1
1
  import * as i1$2 from '@angular/common';
2
2
  import { CommonModule, formatDate, NgClass, AsyncPipe, NgStyle, DatePipe, NgTemplateOutlet } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { ViewChild, Input, CUSTOM_ELEMENTS_SCHEMA, Component, Inject, Pipe, inject, EventEmitter, forwardRef, Output, HostBinding, Injectable, DestroyRef, TemplateRef, ContentChild, ViewContainerRef, Directive, ContentChildren, Optional, input, effect, ViewChildren, computed } from '@angular/core';
4
+ import { ViewChild, Input, CUSTOM_ELEMENTS_SCHEMA, Component, Inject, Pipe, inject, EventEmitter, forwardRef, Output, HostBinding, Injectable, DestroyRef, TemplateRef, ContentChild, ViewContainerRef, Directive, ContentChildren, Optional, input, effect, ViewChildren, output, signal, ElementRef, computed, HostListener } from '@angular/core';
5
5
  import * as i1 from '@angular/forms';
6
- import { FormsModule, ControlContainer, ReactiveFormsModule, NG_VALUE_ACCESSOR, FormGroup, FormControl, FormGroupDirective } from '@angular/forms';
6
+ import { FormsModule, ControlContainer, ReactiveFormsModule, NG_VALUE_ACCESSOR, FormGroup, FormControl, FormGroupDirective, Validators } from '@angular/forms';
7
7
  import * as i1$1 from '@angular/material/snack-bar';
8
8
  import { MAT_SNACK_BAR_DATA } from '@angular/material/snack-bar';
9
9
  import { interval, isObservable, of, Subject, startWith, takeUntil, map, debounceTime, distinctUntilChanged, switchMap, filter } from 'rxjs';
@@ -35,20 +35,19 @@ import { isEqual } from 'lodash-es';
35
35
  import { map as map$1 } from 'rxjs/operators';
36
36
  import * as i2$2 from 'ngx-skeleton-loader';
37
37
  import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
38
- import * as i2$6 from 'ngx-file-drop';
38
+ import * as i2$5 from 'ngx-file-drop';
39
39
  import { NgxFileDropModule } from 'ngx-file-drop';
40
40
  import * as i1$4 from '@angular/material/dialog';
41
- import { MAT_DIALOG_DATA, MatDialogContent, MatDialogConfig } from '@angular/material/dialog';
41
+ import { MAT_DIALOG_DATA, MatDialogContent, MatDialogConfig, MatDialog } from '@angular/material/dialog';
42
42
  import * as i2$3 from '@angular/material/autocomplete';
43
43
  import { MatAutocompleteModule } from '@angular/material/autocomplete';
44
44
  import * as i2$4 from '@angular/cdk/text-field';
45
45
  import { TextFieldModule } from '@angular/cdk/text-field';
46
- import * as i2$5 from '@angular/material/timepicker';
47
- import { MatTimepickerModule } from '@angular/material/timepicker';
48
46
  import * as i1$5 from '@angular/platform-browser';
49
47
  import * as i1$6 from '@angular/material/stepper';
50
48
  import { MatStepperModule } from '@angular/material/stepper';
51
49
  import { MatFormField as MatFormField$1 } from '@angular/material/form-field';
50
+ import { ENTER, COMMA } from '@angular/cdk/keycodes';
52
51
  import { signalStoreFeature, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
53
52
 
54
53
  class CarouselComponent {
@@ -56,8 +55,46 @@ class CarouselComponent {
56
55
  height = 300;
57
56
  direction = 'horizontal';
58
57
  autoplay = true;
58
+ objectFit = 'cover';
59
59
  swiperRef;
60
+ lightboxBackdrop;
60
61
  activeIndex = 0;
62
+ lightboxIndex = null;
63
+ lightboxShouldFocus = false;
64
+ get lightboxOpen() {
65
+ return this.lightboxIndex !== null;
66
+ }
67
+ openLightbox(index) {
68
+ this.lightboxIndex = index;
69
+ this.lightboxShouldFocus = true;
70
+ }
71
+ ngAfterViewChecked() {
72
+ if (this.lightboxShouldFocus && this.lightboxBackdrop) {
73
+ this.lightboxShouldFocus = false;
74
+ setTimeout(() => this.lightboxBackdrop?.nativeElement.focus(), 0);
75
+ }
76
+ }
77
+ closeLightbox() {
78
+ this.lightboxIndex = null;
79
+ }
80
+ lightboxPrev() {
81
+ if (this.lightboxIndex === null)
82
+ return;
83
+ this.lightboxIndex = (this.lightboxIndex - 1 + this.pictures.length) % this.pictures.length;
84
+ }
85
+ lightboxNext() {
86
+ if (this.lightboxIndex === null)
87
+ return;
88
+ this.lightboxIndex = (this.lightboxIndex + 1) % this.pictures.length;
89
+ }
90
+ onLightboxKeydown(event) {
91
+ if (event.key === 'Escape')
92
+ this.closeLightbox();
93
+ if (event.key === 'ArrowLeft')
94
+ this.lightboxPrev();
95
+ if (event.key === 'ArrowRight')
96
+ this.lightboxNext();
97
+ }
61
98
  ngAfterViewInit() {
62
99
  const el = this.swiperRef.nativeElement;
63
100
  Object.assign(el, { autoplay: this.autoplay });
@@ -89,11 +126,11 @@ class CarouselComponent {
89
126
  this.swiperRef.nativeElement.swiper.slideTo(index);
90
127
  }
91
128
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: CarouselComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
92
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: CarouselComponent, isStandalone: true, selector: "ud-carousel", inputs: { pictures: "pictures", height: "height", direction: "direction", autoplay: "autoplay" }, viewQueries: [{ propertyName: "swiperRef", first: true, predicate: ["swiperRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"ud-carousel\">\n <swiper-container\n #swiperRef\n init=\"false\"\n [slidesPerView]=\"1\"\n [pagination]=\"false\"\n [direction]=\"direction\"\n [style.height.px]=\"height\">\n @for (picture of pictures; track $index) {\n <swiper-slide>\n <img [src]=\"picture\" alt=\"Slide {{ $index + 1 }}\" />\n </swiper-slide>\n }\n </swiper-container>\n\n <button class=\"ud-nav ud-nav-prev\" (click)=\"prev()\" aria-label=\"Previous slide\">\n <span class=\"material-icons-outlined\">chevron_left</span>\n </button>\n <button class=\"ud-nav ud-nav-next\" (click)=\"next()\" aria-label=\"Next slide\">\n <span class=\"material-icons-outlined\">chevron_right</span>\n </button>\n\n <div class=\"ud-pagination\">\n @for (picture of pictures; track $index) {\n <button\n class=\"ud-dot\"\n [class.active]=\"activeIndex === $index\"\n (click)=\"goTo($index)\"\n [attr.aria-label]=\"'Go to slide ' + ($index + 1)\">\n </button>\n }\n </div>\n</div>\n", styles: [":host{display:block;width:100%}.ud-carousel{position:relative;width:100%;border-radius:12px;overflow:hidden}.ud-carousel:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:80px;background:linear-gradient(to top,rgba(15,20,30,.6) 0%,transparent 100%);border-radius:0 0 12px 12px;pointer-events:none;z-index:5}swiper-container{width:100%;border-radius:12px}swiper-slide{display:flex;justify-content:center;align-items:center;background:#1b2535;overflow:hidden}swiper-slide img{display:block;width:100%;height:100%;object-fit:cover}.ud-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10;display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;border:none;background:#0f141e73;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);color:#fff;cursor:pointer;opacity:.8;transition:background .2s ease,opacity .2s ease,transform .2s ease}.ud-nav span{font-size:22px;line-height:1;display:flex}.ud-nav:hover{background:#0f141ed1;opacity:1;transform:translateY(-50%) scale(1.08)}.ud-nav.ud-nav-prev{left:12px}.ud-nav.ud-nav-next{right:12px}.ud-pagination{position:absolute;bottom:14px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:6px;z-index:10}.ud-dot{display:block;height:8px;width:8px;border-radius:100px;border:none;padding:0;background:#ffffff73;cursor:pointer;transition:width .3s cubic-bezier(.34,1.56,.64,1),background .25s ease}.ud-dot.active{width:28px;background:#fff}.ud-dot:hover:not(.active){background:#ffffffbf}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
129
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: CarouselComponent, isStandalone: true, selector: "ud-carousel", inputs: { pictures: "pictures", height: "height", direction: "direction", autoplay: "autoplay", objectFit: "objectFit" }, viewQueries: [{ propertyName: "swiperRef", first: true, predicate: ["swiperRef"], descendants: true }, { propertyName: "lightboxBackdrop", first: true, predicate: ["lightboxBackdrop"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"ud-carousel\">\n <swiper-container\n #swiperRef\n init=\"false\"\n [slidesPerView]=\"1\"\n [pagination]=\"false\"\n [direction]=\"direction\"\n [style.height.px]=\"height\">\n @for (picture of pictures; track $index) {\n <swiper-slide>\n <img\n [src]=\"picture\"\n alt=\"Slide {{ $index + 1 }}\"\n [style.object-fit]=\"objectFit\"\n (click)=\"openLightbox($index)\"\n class=\"ud-slide-img\" />\n </swiper-slide>\n }\n </swiper-container>\n\n <button class=\"ud-nav ud-nav-prev\" (click)=\"prev()\" aria-label=\"Previous slide\">\n <span class=\"material-icons-outlined\">chevron_left</span>\n </button>\n <button class=\"ud-nav ud-nav-next\" (click)=\"next()\" aria-label=\"Next slide\">\n <span class=\"material-icons-outlined\">chevron_right</span>\n </button>\n\n <div class=\"ud-pagination\">\n @for (picture of pictures; track $index) {\n <button\n class=\"ud-dot\"\n [class.active]=\"activeIndex === $index\"\n (click)=\"goTo($index)\"\n [attr.aria-label]=\"'Go to slide ' + ($index + 1)\">\n </button>\n }\n </div>\n</div>\n\n@if (lightboxOpen) {\n <div\n #lightboxBackdrop\n class=\"ud-lightbox-backdrop\"\n (click)=\"closeLightbox()\"\n (keydown)=\"onLightboxKeydown($event)\"\n tabindex=\"0\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Image fullscreen view\">\n\n <button class=\"ud-lightbox-close\" (click)=\"closeLightbox()\" aria-label=\"Close\">\n <span class=\"material-icons-outlined\">close</span>\n </button>\n\n <button\n class=\"ud-lightbox-nav ud-lightbox-prev\"\n (click)=\"$event.stopPropagation(); lightboxPrev()\"\n aria-label=\"Previous image\">\n <span class=\"material-icons-outlined\">chevron_left</span>\n </button>\n\n <div class=\"ud-lightbox-img-wrap\" (click)=\"$event.stopPropagation()\">\n <img\n [src]=\"pictures[lightboxIndex!]\"\n alt=\"Fullscreen image {{ lightboxIndex! + 1 }}\"\n class=\"ud-lightbox-img\" />\n </div>\n\n <button\n class=\"ud-lightbox-nav ud-lightbox-next\"\n (click)=\"$event.stopPropagation(); lightboxNext()\"\n aria-label=\"Next image\">\n <span class=\"material-icons-outlined\">chevron_right</span>\n </button>\n\n <div class=\"ud-lightbox-counter\">\n {{ lightboxIndex! + 1 }} / {{ pictures.length }}\n </div>\n </div>\n}\n", styles: [":host{display:block;width:100%}.ud-carousel{position:relative;width:100%;border-radius:12px;overflow:hidden}.ud-carousel:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:80px;background:linear-gradient(to top,rgba(15,20,30,.6) 0%,transparent 100%);border-radius:0 0 12px 12px;pointer-events:none;z-index:5}swiper-container{width:100%;border-radius:12px}swiper-slide{display:flex;justify-content:center;align-items:center;background:#1b2535;overflow:hidden}swiper-slide .ud-slide-img{display:block;width:100%;height:100%;cursor:zoom-in}.ud-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10;display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;border:none;background:#0f141e73;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);color:#fff;cursor:pointer;opacity:.8;transition:background .2s ease,opacity .2s ease,transform .2s ease}.ud-nav span{font-size:22px;line-height:1;display:flex}.ud-nav:hover{background:#0f141ed1;opacity:1;transform:translateY(-50%) scale(1.08)}.ud-nav.ud-nav-prev{left:12px}.ud-nav.ud-nav-next{right:12px}.ud-lightbox-backdrop{position:fixed;inset:0;z-index:1000;background:#000000e0;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;animation:ud-lightbox-in .2s ease;outline:none}@keyframes ud-lightbox-in{0%{opacity:0}to{opacity:1}}.ud-lightbox-img-wrap{max-width:calc(100vw - 120px);max-height:calc(100vh - 80px);display:flex;align-items:center;justify-content:center}.ud-lightbox-img{max-width:100%;max-height:calc(100vh - 80px);border-radius:10px;box-shadow:0 24px 64px #0009;object-fit:contain;display:block;animation:ud-lightbox-scale-in .22s cubic-bezier(.34,1.56,.64,1)}@keyframes ud-lightbox-scale-in{0%{transform:scale(.88);opacity:0}to{transform:scale(1);opacity:1}}.ud-lightbox-close{position:fixed;top:20px;right:20px;z-index:1001;width:44px;height:44px;border-radius:50%;border:none;background:#ffffff1f;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s ease,transform .2s ease}.ud-lightbox-close span{font-size:22px}.ud-lightbox-close:hover{background:#ffffff38;transform:scale(1.1)}.ud-lightbox-nav{position:fixed;top:50%;transform:translateY(-50%);z-index:1001;width:52px;height:52px;border-radius:50%;border:none;background:#ffffff1f;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s ease,transform .2s ease}.ud-lightbox-nav span{font-size:28px}.ud-lightbox-nav:hover{background:#ffffff38}.ud-lightbox-nav.ud-lightbox-prev{left:16px}.ud-lightbox-nav.ud-lightbox-prev:hover{transform:translateY(-50%) scale(1.08)}.ud-lightbox-nav.ud-lightbox-next{right:16px}.ud-lightbox-nav.ud-lightbox-next:hover{transform:translateY(-50%) scale(1.08)}.ud-lightbox-counter{position:fixed;bottom:20px;left:50%;transform:translate(-50%);color:#ffffffb3;font-size:14px;font-weight:500;letter-spacing:.05em;z-index:1001}.ud-pagination{position:absolute;bottom:14px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:6px;z-index:10}.ud-dot{display:block;height:8px;width:8px;border-radius:100px;border:none;padding:0;background:#ffffff73;cursor:pointer;transition:width .3s cubic-bezier(.34,1.56,.64,1),background .25s ease}.ud-dot.active{width:28px;background:#fff}.ud-dot:hover:not(.active){background:#ffffffbf}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
93
130
  }
94
131
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: CarouselComponent, decorators: [{
95
132
  type: Component,
96
- args: [{ selector: 'ud-carousel', imports: [CommonModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<div class=\"ud-carousel\">\n <swiper-container\n #swiperRef\n init=\"false\"\n [slidesPerView]=\"1\"\n [pagination]=\"false\"\n [direction]=\"direction\"\n [style.height.px]=\"height\">\n @for (picture of pictures; track $index) {\n <swiper-slide>\n <img [src]=\"picture\" alt=\"Slide {{ $index + 1 }}\" />\n </swiper-slide>\n }\n </swiper-container>\n\n <button class=\"ud-nav ud-nav-prev\" (click)=\"prev()\" aria-label=\"Previous slide\">\n <span class=\"material-icons-outlined\">chevron_left</span>\n </button>\n <button class=\"ud-nav ud-nav-next\" (click)=\"next()\" aria-label=\"Next slide\">\n <span class=\"material-icons-outlined\">chevron_right</span>\n </button>\n\n <div class=\"ud-pagination\">\n @for (picture of pictures; track $index) {\n <button\n class=\"ud-dot\"\n [class.active]=\"activeIndex === $index\"\n (click)=\"goTo($index)\"\n [attr.aria-label]=\"'Go to slide ' + ($index + 1)\">\n </button>\n }\n </div>\n</div>\n", styles: [":host{display:block;width:100%}.ud-carousel{position:relative;width:100%;border-radius:12px;overflow:hidden}.ud-carousel:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:80px;background:linear-gradient(to top,rgba(15,20,30,.6) 0%,transparent 100%);border-radius:0 0 12px 12px;pointer-events:none;z-index:5}swiper-container{width:100%;border-radius:12px}swiper-slide{display:flex;justify-content:center;align-items:center;background:#1b2535;overflow:hidden}swiper-slide img{display:block;width:100%;height:100%;object-fit:cover}.ud-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10;display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;border:none;background:#0f141e73;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);color:#fff;cursor:pointer;opacity:.8;transition:background .2s ease,opacity .2s ease,transform .2s ease}.ud-nav span{font-size:22px;line-height:1;display:flex}.ud-nav:hover{background:#0f141ed1;opacity:1;transform:translateY(-50%) scale(1.08)}.ud-nav.ud-nav-prev{left:12px}.ud-nav.ud-nav-next{right:12px}.ud-pagination{position:absolute;bottom:14px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:6px;z-index:10}.ud-dot{display:block;height:8px;width:8px;border-radius:100px;border:none;padding:0;background:#ffffff73;cursor:pointer;transition:width .3s cubic-bezier(.34,1.56,.64,1),background .25s ease}.ud-dot.active{width:28px;background:#fff}.ud-dot:hover:not(.active){background:#ffffffbf}\n"] }]
133
+ args: [{ selector: 'ud-carousel', imports: [CommonModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<div class=\"ud-carousel\">\n <swiper-container\n #swiperRef\n init=\"false\"\n [slidesPerView]=\"1\"\n [pagination]=\"false\"\n [direction]=\"direction\"\n [style.height.px]=\"height\">\n @for (picture of pictures; track $index) {\n <swiper-slide>\n <img\n [src]=\"picture\"\n alt=\"Slide {{ $index + 1 }}\"\n [style.object-fit]=\"objectFit\"\n (click)=\"openLightbox($index)\"\n class=\"ud-slide-img\" />\n </swiper-slide>\n }\n </swiper-container>\n\n <button class=\"ud-nav ud-nav-prev\" (click)=\"prev()\" aria-label=\"Previous slide\">\n <span class=\"material-icons-outlined\">chevron_left</span>\n </button>\n <button class=\"ud-nav ud-nav-next\" (click)=\"next()\" aria-label=\"Next slide\">\n <span class=\"material-icons-outlined\">chevron_right</span>\n </button>\n\n <div class=\"ud-pagination\">\n @for (picture of pictures; track $index) {\n <button\n class=\"ud-dot\"\n [class.active]=\"activeIndex === $index\"\n (click)=\"goTo($index)\"\n [attr.aria-label]=\"'Go to slide ' + ($index + 1)\">\n </button>\n }\n </div>\n</div>\n\n@if (lightboxOpen) {\n <div\n #lightboxBackdrop\n class=\"ud-lightbox-backdrop\"\n (click)=\"closeLightbox()\"\n (keydown)=\"onLightboxKeydown($event)\"\n tabindex=\"0\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Image fullscreen view\">\n\n <button class=\"ud-lightbox-close\" (click)=\"closeLightbox()\" aria-label=\"Close\">\n <span class=\"material-icons-outlined\">close</span>\n </button>\n\n <button\n class=\"ud-lightbox-nav ud-lightbox-prev\"\n (click)=\"$event.stopPropagation(); lightboxPrev()\"\n aria-label=\"Previous image\">\n <span class=\"material-icons-outlined\">chevron_left</span>\n </button>\n\n <div class=\"ud-lightbox-img-wrap\" (click)=\"$event.stopPropagation()\">\n <img\n [src]=\"pictures[lightboxIndex!]\"\n alt=\"Fullscreen image {{ lightboxIndex! + 1 }}\"\n class=\"ud-lightbox-img\" />\n </div>\n\n <button\n class=\"ud-lightbox-nav ud-lightbox-next\"\n (click)=\"$event.stopPropagation(); lightboxNext()\"\n aria-label=\"Next image\">\n <span class=\"material-icons-outlined\">chevron_right</span>\n </button>\n\n <div class=\"ud-lightbox-counter\">\n {{ lightboxIndex! + 1 }} / {{ pictures.length }}\n </div>\n </div>\n}\n", styles: [":host{display:block;width:100%}.ud-carousel{position:relative;width:100%;border-radius:12px;overflow:hidden}.ud-carousel:after{content:\"\";position:absolute;bottom:0;left:0;right:0;height:80px;background:linear-gradient(to top,rgba(15,20,30,.6) 0%,transparent 100%);border-radius:0 0 12px 12px;pointer-events:none;z-index:5}swiper-container{width:100%;border-radius:12px}swiper-slide{display:flex;justify-content:center;align-items:center;background:#1b2535;overflow:hidden}swiper-slide .ud-slide-img{display:block;width:100%;height:100%;cursor:zoom-in}.ud-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:10;display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;border:none;background:#0f141e73;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);color:#fff;cursor:pointer;opacity:.8;transition:background .2s ease,opacity .2s ease,transform .2s ease}.ud-nav span{font-size:22px;line-height:1;display:flex}.ud-nav:hover{background:#0f141ed1;opacity:1;transform:translateY(-50%) scale(1.08)}.ud-nav.ud-nav-prev{left:12px}.ud-nav.ud-nav-next{right:12px}.ud-lightbox-backdrop{position:fixed;inset:0;z-index:1000;background:#000000e0;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;animation:ud-lightbox-in .2s ease;outline:none}@keyframes ud-lightbox-in{0%{opacity:0}to{opacity:1}}.ud-lightbox-img-wrap{max-width:calc(100vw - 120px);max-height:calc(100vh - 80px);display:flex;align-items:center;justify-content:center}.ud-lightbox-img{max-width:100%;max-height:calc(100vh - 80px);border-radius:10px;box-shadow:0 24px 64px #0009;object-fit:contain;display:block;animation:ud-lightbox-scale-in .22s cubic-bezier(.34,1.56,.64,1)}@keyframes ud-lightbox-scale-in{0%{transform:scale(.88);opacity:0}to{transform:scale(1);opacity:1}}.ud-lightbox-close{position:fixed;top:20px;right:20px;z-index:1001;width:44px;height:44px;border-radius:50%;border:none;background:#ffffff1f;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s ease,transform .2s ease}.ud-lightbox-close span{font-size:22px}.ud-lightbox-close:hover{background:#ffffff38;transform:scale(1.1)}.ud-lightbox-nav{position:fixed;top:50%;transform:translateY(-50%);z-index:1001;width:52px;height:52px;border-radius:50%;border:none;background:#ffffff1f;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .2s ease,transform .2s ease}.ud-lightbox-nav span{font-size:28px}.ud-lightbox-nav:hover{background:#ffffff38}.ud-lightbox-nav.ud-lightbox-prev{left:16px}.ud-lightbox-nav.ud-lightbox-prev:hover{transform:translateY(-50%) scale(1.08)}.ud-lightbox-nav.ud-lightbox-next{right:16px}.ud-lightbox-nav.ud-lightbox-next:hover{transform:translateY(-50%) scale(1.08)}.ud-lightbox-counter{position:fixed;bottom:20px;left:50%;transform:translate(-50%);color:#ffffffb3;font-size:14px;font-weight:500;letter-spacing:.05em;z-index:1001}.ud-pagination{position:absolute;bottom:14px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:6px;z-index:10}.ud-dot{display:block;height:8px;width:8px;border-radius:100px;border:none;padding:0;background:#ffffff73;cursor:pointer;transition:width .3s cubic-bezier(.34,1.56,.64,1),background .25s ease}.ud-dot.active{width:28px;background:#fff}.ud-dot:hover:not(.active){background:#ffffffbf}\n"] }]
97
134
  }], propDecorators: { pictures: [{
98
135
  type: Input
99
136
  }], height: [{
@@ -102,9 +139,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImpo
102
139
  type: Input
103
140
  }], autoplay: [{
104
141
  type: Input
142
+ }], objectFit: [{
143
+ type: Input
105
144
  }], swiperRef: [{
106
145
  type: ViewChild,
107
146
  args: ['swiperRef']
147
+ }], lightboxBackdrop: [{
148
+ type: ViewChild,
149
+ args: ['lightboxBackdrop']
108
150
  }] } });
109
151
 
110
152
  class CustomInputComponent {
@@ -2233,56 +2275,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImpo
2233
2275
  type: Input
2234
2276
  }] } });
2235
2277
 
2236
- /**
2237
- * Styled time picker that participates in the parent FormGroup.
2238
- *
2239
- * Usage (interval mode — value is in minutes):
2240
- * <ud-time-picker
2241
- * controlName="checkInTime"
2242
- * label="Check-in time"
2243
- * [intervalMinutes]="30" />
2244
- *
2245
- * Usage (custom options mode):
2246
- * <ud-time-picker
2247
- * controlName="checkInTime"
2248
- * label="Check-in time"
2249
- * [options]="timeOptions" />
2250
- */
2251
2278
  class TimePickerComponent {
2252
2279
  controlName;
2253
2280
  label = '';
2254
2281
  placeholder = '';
2255
- icon = 'schedule';
2256
- iconFontSet = 'material-icons-outlined';
2257
- /**
2258
- * Minutes between each option in the dropdown (e.g. 30 → every half hour).
2259
- * Omit to use Material's default (30 min). Ignored when `options` is provided.
2260
- * Internally converted to seconds for mat-timepicker.
2261
- */
2262
2282
  intervalMinutes;
2263
- /** Pre-defined list of selectable times. When set, `intervalMinutes` is ignored. */
2264
2283
  options;
2265
2284
  disabled = false;
2266
2285
  hint = '';
2267
- focused = false;
2268
- controlContainer = inject(ControlContainer);
2269
- get control() {
2270
- return this.controlContainer.control.get(this.controlName);
2286
+ timeOptions = [];
2287
+ ngOnInit() {
2288
+ this.timeOptions = this.buildOptions();
2271
2289
  }
2272
2290
  ngOnChanges(changes) {
2273
- if (changes['disabled']) {
2274
- this.disabled ? this.control.disable() : this.control.enable();
2291
+ if (changes['intervalMinutes'] || changes['options']) {
2292
+ this.timeOptions = this.buildOptions();
2275
2293
  }
2276
2294
  }
2277
- get _intervalSeconds() {
2278
- return this.intervalMinutes != null ? this.intervalMinutes * 60 : null;
2295
+ buildOptions() {
2296
+ if (this.options?.length) {
2297
+ return this.options.map(o => {
2298
+ const label = o.label ?? this.formatTime(o.value);
2299
+ return { value: label, label };
2300
+ });
2301
+ }
2302
+ const interval = this.intervalMinutes ?? 30;
2303
+ const result = [];
2304
+ for (let h = 0; h < 24; h++) {
2305
+ for (let m = 0; m < 60; m += interval) {
2306
+ const d = new Date();
2307
+ d.setHours(h, m, 0, 0);
2308
+ const label = this.formatTime(d);
2309
+ result.push({ value: label, label });
2310
+ }
2311
+ }
2312
+ return result;
2313
+ }
2314
+ formatTime(d) {
2315
+ return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
2279
2316
  }
2280
2317
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: TimePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2281
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: TimePickerComponent, isStandalone: true, selector: "ud-time-picker", inputs: { controlName: "controlName", label: "label", placeholder: "placeholder", icon: "icon", iconFontSet: "iconFontSet", intervalMinutes: "intervalMinutes", options: "options", disabled: "disabled", hint: "hint" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"ud-input\"\n [class.ud-input--focused]=\"focused\"\n [class.ud-input--disabled]=\"control.disabled\">\n @if (label) {\n <label class=\"ud-input__label\" [for]=\"'ud-input-' + controlName\">{{ label }}</label>\n }\n <div class=\"ud-input__wrapper\">\n @if (icon) {\n <mat-icon class=\"ud-input__icon\" [fontSet]=\"iconFontSet\">{{ icon }}</mat-icon>\n }\n @if (options && options.length) {\n <input\n class=\"ud-input__field\"\n [id]=\"'ud-input-' + controlName\"\n [formControlName]=\"controlName\"\n [placeholder]=\"placeholder\"\n [matTimepicker]=\"optionsPicker\"\n (focus)=\"focused = true\"\n (blur)=\"focused = false\" />\n <mat-timepicker-toggle class=\"ud-timepicker-toggle\" [for]=\"optionsPicker\">\n <mat-icon matTimepickerToggleIcon fontSet=\"material-icons-outlined\">schedule</mat-icon>\n </mat-timepicker-toggle>\n <mat-timepicker #optionsPicker [options]=\"options\" />\n } @else {\n <input\n class=\"ud-input__field\"\n [id]=\"'ud-input-' + controlName\"\n [formControlName]=\"controlName\"\n [placeholder]=\"placeholder\"\n [matTimepicker]=\"intervalPicker\"\n (focus)=\"focused = true\"\n (blur)=\"focused = false\" />\n <mat-timepicker-toggle class=\"ud-timepicker-toggle\" [for]=\"intervalPicker\">\n <mat-icon matTimepickerToggleIcon fontSet=\"material-icons-outlined\">schedule</mat-icon>\n </mat-timepicker-toggle>\n <mat-timepicker #intervalPicker [interval]=\"_intervalSeconds\" />\n }\n </div>\n @if (hint) {\n <span class=\"ud-input__hint\">{{ hint }}</span>\n }\n</div>\n", styles: [":host{display:block;width:100%}.ud-input{display:flex;flex-direction:column;gap:5px;width:100%}.ud-input__label{font-family:DM Sans,system-ui,sans-serif;font-size:13px;font-weight:500;color:#2a3548;line-height:1;padding-left:1px}.ud-input__wrapper{display:flex;align-items:center;background:#f8fafc;border:1px solid #d8dde6;border-radius:8px;padding:0 12px;gap:7px;overflow:hidden;transition:border-color .18s ease,box-shadow .18s ease,background .18s ease}.ud-input--focused .ud-input__wrapper{border-color:#1b2535;box-shadow:0 0 0 3px #1b253514;background:#fff}.ud-input--error .ud-input__wrapper{border-color:#e53935;box-shadow:0 0 0 3px #e539351a}.ud-input--error.ud-input--focused .ud-input__wrapper{border-color:#e53935}.ud-input--disabled .ud-input__wrapper{background:#f4f5f7;border-color:#e8eaef;cursor:not-allowed;opacity:.6}.ud-input__icon{flex-shrink:0;font-size:18px;width:18px;height:18px;color:#6b7585;transition:color .18s ease;line-height:1}.ud-input--focused .ud-input__icon{color:#1b2535}.ud-input__field{flex:1;min-width:0;border:none;background:transparent;outline:none;font-family:DM Sans,system-ui,sans-serif;font-size:14px;color:#2a3548;width:100%}.ud-input__field::placeholder{color:#9099a8}.ud-input__field:disabled{cursor:not-allowed;color:#9099a8}.ud-input__hint{font-family:DM Sans,system-ui,sans-serif;font-size:12px;color:#6b7585;line-height:1.3}.ud-input__suffix{flex-shrink:0;font-size:18px;width:18px;height:18px;color:#6b7585}.ud-input__loading{animation:ud-fw-spin .8s linear infinite}@keyframes ud-fw-spin{to{transform:rotate(360deg)}}.ud-timepicker-toggle{flex-shrink:0;margin-right:-4px}.ud-timepicker-toggle ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{width:32px;height:32px;padding:4px;color:#6b7585;transition:color .18s ease}.ud-timepicker-toggle ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base:hover{color:#1b2535}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTimepickerModule }, { kind: "component", type: i2$5.MatTimepicker, selector: "mat-timepicker", inputs: ["interval", "options", "disableRipple", "aria-label", "aria-labelledby"], outputs: ["selected", "opened", "closed"], exportAs: ["matTimepicker"] }, { kind: "directive", type: i2$5.MatTimepickerInput, selector: "input[matTimepicker]", inputs: ["value", "matTimepicker", "matTimepickerMin", "matTimepickerMax", "disabled"], outputs: ["valueChange"], exportAs: ["matTimepickerInput"] }, { kind: "component", type: i2$5.MatTimepickerToggle, selector: "mat-timepicker-toggle", inputs: ["for", "aria-label", "aria-labelledby", "disabled", "tabIndex", "disableRipple"], exportAs: ["matTimepickerToggle"] }], viewProviders: [{ provide: ControlContainer, useFactory: () => inject(ControlContainer, { skipSelf: true }) }] });
2318
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.19", type: TimePickerComponent, isStandalone: true, selector: "ud-time-picker", inputs: { controlName: "controlName", label: "label", placeholder: "placeholder", intervalMinutes: "intervalMinutes", options: "options", disabled: "disabled", hint: "hint" }, usesOnChanges: true, ngImport: i0, template: "<ud-autocomplete\n [controlName]=\"controlName\"\n [label]=\"label\"\n [placeholder]=\"placeholder\"\n [hint]=\"hint\"\n [disabled]=\"disabled\"\n icon=\"schedule\"\n [options]=\"timeOptions\" />\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "ud-autocomplete", inputs: ["controlName", "label", "placeholder", "icon", "iconFontSet", "options", "loading", "disabled", "hint", "size"], outputs: ["searchChange"] }], viewProviders: [{ provide: ControlContainer, useFactory: () => inject(ControlContainer, { skipSelf: true }) }] });
2282
2319
  }
2283
2320
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: TimePickerComponent, decorators: [{
2284
2321
  type: Component,
2285
- args: [{ selector: 'ud-time-picker', standalone: true, imports: [CommonModule, ReactiveFormsModule, MatIcon, MatTimepickerModule], viewProviders: [{ provide: ControlContainer, useFactory: () => inject(ControlContainer, { skipSelf: true }) }], template: "<div\n class=\"ud-input\"\n [class.ud-input--focused]=\"focused\"\n [class.ud-input--disabled]=\"control.disabled\">\n @if (label) {\n <label class=\"ud-input__label\" [for]=\"'ud-input-' + controlName\">{{ label }}</label>\n }\n <div class=\"ud-input__wrapper\">\n @if (icon) {\n <mat-icon class=\"ud-input__icon\" [fontSet]=\"iconFontSet\">{{ icon }}</mat-icon>\n }\n @if (options && options.length) {\n <input\n class=\"ud-input__field\"\n [id]=\"'ud-input-' + controlName\"\n [formControlName]=\"controlName\"\n [placeholder]=\"placeholder\"\n [matTimepicker]=\"optionsPicker\"\n (focus)=\"focused = true\"\n (blur)=\"focused = false\" />\n <mat-timepicker-toggle class=\"ud-timepicker-toggle\" [for]=\"optionsPicker\">\n <mat-icon matTimepickerToggleIcon fontSet=\"material-icons-outlined\">schedule</mat-icon>\n </mat-timepicker-toggle>\n <mat-timepicker #optionsPicker [options]=\"options\" />\n } @else {\n <input\n class=\"ud-input__field\"\n [id]=\"'ud-input-' + controlName\"\n [formControlName]=\"controlName\"\n [placeholder]=\"placeholder\"\n [matTimepicker]=\"intervalPicker\"\n (focus)=\"focused = true\"\n (blur)=\"focused = false\" />\n <mat-timepicker-toggle class=\"ud-timepicker-toggle\" [for]=\"intervalPicker\">\n <mat-icon matTimepickerToggleIcon fontSet=\"material-icons-outlined\">schedule</mat-icon>\n </mat-timepicker-toggle>\n <mat-timepicker #intervalPicker [interval]=\"_intervalSeconds\" />\n }\n </div>\n @if (hint) {\n <span class=\"ud-input__hint\">{{ hint }}</span>\n }\n</div>\n", styles: [":host{display:block;width:100%}.ud-input{display:flex;flex-direction:column;gap:5px;width:100%}.ud-input__label{font-family:DM Sans,system-ui,sans-serif;font-size:13px;font-weight:500;color:#2a3548;line-height:1;padding-left:1px}.ud-input__wrapper{display:flex;align-items:center;background:#f8fafc;border:1px solid #d8dde6;border-radius:8px;padding:0 12px;gap:7px;overflow:hidden;transition:border-color .18s ease,box-shadow .18s ease,background .18s ease}.ud-input--focused .ud-input__wrapper{border-color:#1b2535;box-shadow:0 0 0 3px #1b253514;background:#fff}.ud-input--error .ud-input__wrapper{border-color:#e53935;box-shadow:0 0 0 3px #e539351a}.ud-input--error.ud-input--focused .ud-input__wrapper{border-color:#e53935}.ud-input--disabled .ud-input__wrapper{background:#f4f5f7;border-color:#e8eaef;cursor:not-allowed;opacity:.6}.ud-input__icon{flex-shrink:0;font-size:18px;width:18px;height:18px;color:#6b7585;transition:color .18s ease;line-height:1}.ud-input--focused .ud-input__icon{color:#1b2535}.ud-input__field{flex:1;min-width:0;border:none;background:transparent;outline:none;font-family:DM Sans,system-ui,sans-serif;font-size:14px;color:#2a3548;width:100%}.ud-input__field::placeholder{color:#9099a8}.ud-input__field:disabled{cursor:not-allowed;color:#9099a8}.ud-input__hint{font-family:DM Sans,system-ui,sans-serif;font-size:12px;color:#6b7585;line-height:1.3}.ud-input__suffix{flex-shrink:0;font-size:18px;width:18px;height:18px;color:#6b7585}.ud-input__loading{animation:ud-fw-spin .8s linear infinite}@keyframes ud-fw-spin{to{transform:rotate(360deg)}}.ud-timepicker-toggle{flex-shrink:0;margin-right:-4px}.ud-timepicker-toggle ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{width:32px;height:32px;padding:4px;color:#6b7585;transition:color .18s ease}.ud-timepicker-toggle ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base:hover{color:#1b2535}\n"] }]
2322
+ args: [{ selector: 'ud-time-picker', standalone: true, imports: [AutocompleteComponent], viewProviders: [{ provide: ControlContainer, useFactory: () => inject(ControlContainer, { skipSelf: true }) }], template: "<ud-autocomplete\n [controlName]=\"controlName\"\n [label]=\"label\"\n [placeholder]=\"placeholder\"\n [hint]=\"hint\"\n [disabled]=\"disabled\"\n icon=\"schedule\"\n [options]=\"timeOptions\" />\n" }]
2286
2323
  }], propDecorators: { controlName: [{
2287
2324
  type: Input,
2288
2325
  args: [{ required: true }]
@@ -2290,10 +2327,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImpo
2290
2327
  type: Input
2291
2328
  }], placeholder: [{
2292
2329
  type: Input
2293
- }], icon: [{
2294
- type: Input
2295
- }], iconFontSet: [{
2296
- type: Input
2297
2330
  }], intervalMinutes: [{
2298
2331
  type: Input
2299
2332
  }], options: [{
@@ -2469,6 +2502,10 @@ class ModalComponent {
2469
2502
  if (this.data.showClose != undefined)
2470
2503
  this.showClose = this.data.showClose;
2471
2504
  }
2505
+ onDelete() {
2506
+ this.data.delete?.();
2507
+ this.dialogRef?.close();
2508
+ }
2472
2509
  close() {
2473
2510
  // If the caller is listening on (cancel) they own the close lifecycle —
2474
2511
  // emit and let them decide. Otherwise fall back to auto-closing the
@@ -2506,7 +2543,7 @@ class ModalComponent {
2506
2543
  }
2507
2544
  }
2508
2545
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: ModalComponent, deps: [{ token: MAT_DIALOG_DATA, optional: true }, { token: i1$4.MatDialogRef, optional: true }], target: i0.ɵɵFactoryTarget.Component });
2509
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: ModalComponent, isStandalone: true, selector: "ud-modal", inputs: { title: "title", eyebrow: "eyebrow", lede: "lede", bodyText: "bodyText", showClose: "showClose", showFooter: "showFooter", confirmLabel: "confirmLabel", cancelLabel: "cancelLabel", confirmDisabled: "confirmDisabled" }, outputs: { confirm: "confirm", cancel: "cancel" }, ngImport: i0, template: "<div class=\"ud-modal-shell\">\n <header class=\"ud-modal-header\">\n @if (eyebrow) {\n <p class=\"ud-modal-eyebrow\">{{ eyebrow | translate | capitalize }}</p>\n }\n @if (title) {\n <h3 class=\"ud-modal-title\">{{ title | translate | capitalize }}</h3>\n }\n @if (lede) {\n <p class=\"ud-modal-lede\">{{ lede | translate | capitalize }}</p>\n }\n </header>\n\n <div class=\"ud-modal-content\" mat-dialog-content>\n @if (pictureUrls && currentPictureIndex != undefined) {\n <div class=\"image-container\">\n <mat-icon class=\"arrow-picture-icon\" (click)=\"previous()\">keyboard_arrow_left</mat-icon>\n <img [src]=\"pictureUrls[currentPictureIndex]\" alt=\"Picture\" />\n <mat-icon class=\"arrow-picture-icon\" (click)=\"next()\">keyboard_arrow_right</mat-icon>\n </div>\n <div class=\"image-footer\">\n {{ currentPictureIndex + 1 }} {{ 'names.of' | translate }} {{ pictureUrls.length }}\n </div>\n } @else if (form) {\n <form [formGroup]=\"form\">\n <div class=\"modal-form-fields\">\n @for (formType of data.forms; track formType) {\n @switch (formType.type) {\n @case (modalInputType.INPUT) {\n <ud-text-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [type]=\"formType.inputType ?? 'text'\"\n [placeholder]=\"formType.placeholder ?? ''\" />\n }\n @case (modalInputType.PHONE) {\n <ud-phone-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\" />\n }\n @case (modalInputType.TEXT_AREA) {\n <ud-textarea\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\"\n [minRows]=\"formType.minRows ?? 3\"\n [maxRows]=\"formType.maxRows ?? 6\" />\n }\n @case (modalInputType.OPTIONS) {\n <ud-multi-select\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [options]=\"formType.availableOptions\"\n [multiple]=\"false\" />\n }\n @case (modalInputType.MULTI_SELECT) {\n <ud-multi-select\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [options]=\"formType.availableOptions\"\n [multiple]=\"true\" />\n }\n @case (modalInputType.AUTOCOMPLETE) {\n <ud-autocomplete\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\"\n [options]=\"formType.availableOptions\" />\n }\n @case (modalInputType.DATERANGE) {\n <ud-date-range-input\n [startControlName]=\"'start' + formType.property\"\n [endControlName]=\"'end' + formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\" />\n }\n @case (modalInputType.DATETIME) {\n <ud-date-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [min]=\"formType.min\"\n [max]=\"formType.max\" />\n }\n @case (modalInputType.TIME) {\n <ud-time-picker\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [intervalMinutes]=\"formType.intervalMinutes ?? 30\"\n [options]=\"formType.availableOptions\" />\n }\n @case (modalInputType.CHIPS) {\n <mat-form-field appearance=\"outline\">\n <mat-label>\n {{ formType.title | translate | singular | capitalize }}\n </mat-label>\n <mat-chip-grid #chipGrid [formControlName]=\"formType.property\">\n @for (option of formType.availableOptions; track option) {\n <mat-chip-row (removed)=\"formType.removeOption(option)\">\n {{ option }}\n <button matChipRemove>\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip-row>\n }\n </mat-chip-grid>\n <input\n placeholder=\"New option...\"\n [matChipInputFor]=\"chipGrid\"\n (matChipInputTokenEnd)=\"formType.addOption($event)\" />\n </mat-form-field>\n }\n }\n }\n </div>\n </form>\n } @else if (bodyText) {\n <p class=\"ud-modal-body-text\">{{ bodyText }}</p>\n } @else {\n <ng-content></ng-content>\n }\n </div>\n\n @if (showFooter) {\n <footer class=\"ud-modal-footer\">\n @if (showClose) {\n <ud-button variant=\"stroked\" color=\"secondary\" (click)=\"close()\">\n {{ (cancelLabel ?? 'actions.close') | translate | capitalize }}\n </ud-button>\n }\n <ud-button\n [disabled]=\"!!(form && form.invalid) || confirmDisabled\"\n (click)=\"onAction()\">\n {{ (confirmLabel ?? (form ? 'actions.save' : 'actions.ok')) | translate | capitalize }}\n </ud-button>\n </footer>\n }\n</div>\n", styles: [".ud-modal-shell{display:flex;flex-direction:column;overflow:hidden;min-width:360px;width:100%}.ud-modal-header{position:relative;padding:28px 28px 18px;overflow:hidden}.ud-modal-header:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at 20% 0%,rgba(27,37,53,.1),transparent 60%),radial-gradient(circle at 85% 10%,rgba(58,74,102,.07),transparent 55%);pointer-events:none}.ud-modal-eyebrow{margin:0 0 6px;font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#1b2535}.ud-modal-title{margin:0;font-size:22px;line-height:1.2;font-weight:600;color:#2a3548;letter-spacing:-.01em}.ud-modal-lede{margin:8px 0 0;color:#6b7585;font-size:13.5px;line-height:1.5;max-width:40ch}.ud-modal-content{padding:4px 28px 12px;max-height:60vh;overflow-y:auto}.ud-modal-content.mat-mdc-dialog-content{padding:4px 28px 12px;margin:0;max-height:60vh}.modal-form-fields{display:flex;flex-direction:column;gap:4px;padding-top:8px}ud-text-input,ud-textarea,ud-multi-select,ud-autocomplete,ud-date-input,ud-date-range-input,ud-time-picker,ud-phone-input,mat-form-field{display:block;width:100%}.ud-modal-body-text{margin:4px 0;font-size:14px;color:#4a5568;line-height:1.55}.image-container{display:flex;justify-content:space-between;align-items:center}.image-container .arrow-picture-icon{display:flex;justify-content:center;align-items:center;padding:1rem;font-size:2rem;cursor:pointer;background:#1b2535;color:#fff;border-radius:8px;transition:background .15s ease}.image-container .arrow-picture-icon:hover{background:#253347}.image-container img{width:100%;max-width:460px;border-radius:8px}.image-footer{display:flex;justify-content:center;align-items:center;padding:8px 0 0;font-size:13px;color:#6b7585}.ud-modal-footer{display:flex;justify-content:flex-end;align-items:center;gap:10px;padding:16px 28px 24px;border-top:1px solid #e2e5ea;background:#fbfbfd}\n"], dependencies: [{ kind: "pipe", type: CapitalizePipe, name: "capitalize" }, { kind: "pipe", type: SingularPipe, name: "singular" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "directive", type: MatChipInput, selector: "input[matChipInputFor]", inputs: ["matChipInputFor", "matChipInputAddOnBlur", "matChipInputSeparatorKeyCodes", "placeholder", "id", "disabled"], outputs: ["matChipInputTokenEnd"], exportAs: ["matChipInput", "matChipInputFor"] }, { kind: "component", type: MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "directive", type: MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: TextInputComponent, selector: "ud-text-input", inputs: ["controlName", "label", "placeholder", "type", "icon", "iconFontSet", "loading", "step", "min", "max", "disabled", "hint", "size", "clearable"] }, { kind: "component", type: TextareaComponent, selector: "ud-textarea", inputs: ["controlName", "label", "placeholder", "icon", "iconFontSet", "minRows", "maxRows", "disabled", "hint"] }, { kind: "component", type: MultiSelectComponent, selector: "ud-multi-select", inputs: ["controlName", "label", "icon", "iconFontSet", "options", "multiple", "maxChipsVisible", "moreText", "loading", "disabled", "hint"] }, { kind: "component", type: AutocompleteComponent, selector: "ud-autocomplete", inputs: ["controlName", "label", "placeholder", "icon", "iconFontSet", "options", "loading", "disabled", "hint", "size"], outputs: ["searchChange"] }, { kind: "component", type: DateInputComponent, selector: "ud-date-input", inputs: ["controlName", "label", "placeholder", "icon", "iconFontSet", "min", "max", "disabled", "hint"] }, { kind: "component", type: DateRangeInputComponent, selector: "ud-date-range-input", inputs: ["startControlName", "endControlName", "label", "icon", "iconFontSet", "startPlaceholder", "endPlaceholder", "min", "max", "disabled", "hint"] }, { kind: "component", type: TimePickerComponent, selector: "ud-time-picker", inputs: ["controlName", "label", "placeholder", "icon", "iconFontSet", "intervalMinutes", "options", "disabled", "hint"] }, { kind: "component", type: PhoneInputComponent, selector: "ud-phone-input", inputs: ["controlName", "label", "placeholder", "disabled", "hint", "size"] }, { kind: "component", type: UdButtonComponent, selector: "ud-button", inputs: ["variant", "color", "size", "type", "icon", "iconPosition", "iconFontSet", "loading", "disabled", "fullWidth"] }] });
2546
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: ModalComponent, isStandalone: true, selector: "ud-modal", inputs: { title: "title", eyebrow: "eyebrow", lede: "lede", bodyText: "bodyText", showClose: "showClose", showFooter: "showFooter", confirmLabel: "confirmLabel", cancelLabel: "cancelLabel", confirmDisabled: "confirmDisabled" }, outputs: { confirm: "confirm", cancel: "cancel" }, ngImport: i0, template: "<div class=\"ud-modal-shell\">\n <header class=\"ud-modal-header\">\n @if (eyebrow) {\n <p class=\"ud-modal-eyebrow\">{{ eyebrow | translate | capitalize }}</p>\n }\n @if (title) {\n <h3 class=\"ud-modal-title\">{{ title | translate | capitalize }}</h3>\n }\n @if (lede) {\n <p class=\"ud-modal-lede\">{{ lede | translate | capitalize }}</p>\n }\n </header>\n\n <div class=\"ud-modal-content\" mat-dialog-content>\n @if (pictureUrls && currentPictureIndex != undefined) {\n <div class=\"image-container\">\n <mat-icon class=\"arrow-picture-icon\" (click)=\"previous()\">keyboard_arrow_left</mat-icon>\n <img [src]=\"pictureUrls[currentPictureIndex]\" alt=\"Picture\" />\n <mat-icon class=\"arrow-picture-icon\" (click)=\"next()\">keyboard_arrow_right</mat-icon>\n </div>\n <div class=\"image-footer\">\n {{ currentPictureIndex + 1 }} {{ 'names.of' | translate }} {{ pictureUrls.length }}\n </div>\n } @else if (form) {\n <form [formGroup]=\"form\">\n <div class=\"modal-form-fields\">\n @for (formType of data.forms; track formType) {\n @switch (formType.type) {\n @case (modalInputType.INPUT) {\n <ud-text-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [type]=\"formType.inputType ?? 'text'\"\n [placeholder]=\"formType.placeholder ?? ''\" />\n }\n @case (modalInputType.PHONE) {\n <ud-phone-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\" />\n }\n @case (modalInputType.TEXT_AREA) {\n <ud-textarea\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\"\n [minRows]=\"formType.minRows ?? 3\"\n [maxRows]=\"formType.maxRows ?? 6\" />\n }\n @case (modalInputType.OPTIONS) {\n <ud-multi-select\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [options]=\"formType.availableOptions\"\n [multiple]=\"false\" />\n }\n @case (modalInputType.MULTI_SELECT) {\n <ud-multi-select\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [options]=\"formType.availableOptions\"\n [multiple]=\"true\" />\n }\n @case (modalInputType.AUTOCOMPLETE) {\n <ud-autocomplete\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\"\n [options]=\"formType.availableOptions\" />\n }\n @case (modalInputType.DATERANGE) {\n <ud-date-range-input\n [startControlName]=\"'start' + formType.property\"\n [endControlName]=\"'end' + formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\" />\n }\n @case (modalInputType.DATETIME) {\n <ud-date-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [min]=\"formType.min\"\n [max]=\"formType.max\" />\n }\n @case (modalInputType.TIME) {\n <ud-time-picker\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [intervalMinutes]=\"formType.intervalMinutes ?? 30\"\n [options]=\"formType.availableOptions\" />\n }\n @case (modalInputType.CHIPS) {\n <mat-form-field appearance=\"outline\">\n <mat-label>\n {{ formType.title | translate | singular | capitalize }}\n </mat-label>\n <mat-chip-grid #chipGrid [formControlName]=\"formType.property\">\n @for (option of formType.availableOptions; track option) {\n <mat-chip-row (removed)=\"formType.removeOption(option)\">\n {{ option }}\n <button matChipRemove>\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip-row>\n }\n </mat-chip-grid>\n <input\n placeholder=\"New option...\"\n [matChipInputFor]=\"chipGrid\"\n (matChipInputTokenEnd)=\"formType.addOption($event)\" />\n </mat-form-field>\n }\n }\n }\n </div>\n </form>\n } @else if (bodyText) {\n <p class=\"ud-modal-body-text\">{{ bodyText }}</p>\n } @else {\n <ng-content></ng-content>\n }\n </div>\n\n @if (showFooter) {\n <footer class=\"ud-modal-footer\">\n @if (data.delete) {\n <ud-button variant=\"stroked\" color=\"danger\" class=\"ud-modal-footer__delete\" (click)=\"onDelete()\">\n {{ data.deleteLabel ?? 'Delete' }}\n </ud-button>\n }\n @if (showClose) {\n <ud-button variant=\"stroked\" color=\"secondary\" (click)=\"close()\">\n {{ (cancelLabel ?? 'actions.close') | translate | capitalize }}\n </ud-button>\n }\n <ud-button\n [disabled]=\"!!(form && form.invalid) || confirmDisabled\"\n (click)=\"onAction()\">\n {{ (confirmLabel ?? (form ? 'actions.save' : 'actions.ok')) | translate | capitalize }}\n </ud-button>\n </footer>\n }\n</div>\n", styles: [".ud-modal-shell{display:flex;flex-direction:column;overflow:hidden;min-width:360px;width:100%}.ud-modal-header{position:relative;padding:28px 28px 18px;overflow:hidden}.ud-modal-header:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at 20% 0%,rgba(27,37,53,.1),transparent 60%),radial-gradient(circle at 85% 10%,rgba(58,74,102,.07),transparent 55%);pointer-events:none}.ud-modal-eyebrow{margin:0 0 6px;font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#1b2535}.ud-modal-title{margin:0;font-size:22px;line-height:1.2;font-weight:600;color:#2a3548;letter-spacing:-.01em}.ud-modal-lede{margin:8px 0 0;color:#6b7585;font-size:13.5px;line-height:1.5;max-width:40ch}.ud-modal-content{padding:4px 28px 12px;max-height:60vh;overflow-y:auto}.ud-modal-content.mat-mdc-dialog-content{padding:4px 28px 12px;margin:0;max-height:60vh}.modal-form-fields{display:flex;flex-direction:column;gap:4px;padding-top:8px}ud-text-input,ud-textarea,ud-multi-select,ud-autocomplete,ud-date-input,ud-date-range-input,ud-time-picker,ud-phone-input,mat-form-field{display:block;width:100%}.ud-modal-body-text{margin:4px 0;font-size:14px;color:#4a5568;line-height:1.55}.image-container{display:flex;justify-content:space-between;align-items:center}.image-container .arrow-picture-icon{display:flex;justify-content:center;align-items:center;padding:1rem;font-size:2rem;cursor:pointer;background:#1b2535;color:#fff;border-radius:8px;transition:background .15s ease}.image-container .arrow-picture-icon:hover{background:#253347}.image-container img{width:100%;max-width:460px;border-radius:8px}.image-footer{display:flex;justify-content:center;align-items:center;padding:8px 0 0;font-size:13px;color:#6b7585}.ud-modal-footer{display:flex;justify-content:flex-end;align-items:center;gap:10px;padding:16px 28px 24px;border-top:1px solid #e2e5ea;background:#fbfbfd}.ud-modal-footer__delete{margin-right:auto}\n"], dependencies: [{ kind: "pipe", type: CapitalizePipe, name: "capitalize" }, { kind: "pipe", type: SingularPipe, name: "singular" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "directive", type: MatChipInput, selector: "input[matChipInputFor]", inputs: ["matChipInputFor", "matChipInputAddOnBlur", "matChipInputSeparatorKeyCodes", "placeholder", "id", "disabled"], outputs: ["matChipInputTokenEnd"], exportAs: ["matChipInput", "matChipInputFor"] }, { kind: "component", type: MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "directive", type: MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: TextInputComponent, selector: "ud-text-input", inputs: ["controlName", "label", "placeholder", "type", "icon", "iconFontSet", "loading", "step", "min", "max", "disabled", "hint", "size", "clearable"] }, { kind: "component", type: TextareaComponent, selector: "ud-textarea", inputs: ["controlName", "label", "placeholder", "icon", "iconFontSet", "minRows", "maxRows", "disabled", "hint"] }, { kind: "component", type: MultiSelectComponent, selector: "ud-multi-select", inputs: ["controlName", "label", "icon", "iconFontSet", "options", "multiple", "maxChipsVisible", "moreText", "loading", "disabled", "hint"] }, { kind: "component", type: AutocompleteComponent, selector: "ud-autocomplete", inputs: ["controlName", "label", "placeholder", "icon", "iconFontSet", "options", "loading", "disabled", "hint", "size"], outputs: ["searchChange"] }, { kind: "component", type: DateInputComponent, selector: "ud-date-input", inputs: ["controlName", "label", "placeholder", "icon", "iconFontSet", "min", "max", "disabled", "hint"] }, { kind: "component", type: DateRangeInputComponent, selector: "ud-date-range-input", inputs: ["startControlName", "endControlName", "label", "icon", "iconFontSet", "startPlaceholder", "endPlaceholder", "min", "max", "disabled", "hint"] }, { kind: "component", type: TimePickerComponent, selector: "ud-time-picker", inputs: ["controlName", "label", "placeholder", "intervalMinutes", "options", "disabled", "hint"] }, { kind: "component", type: PhoneInputComponent, selector: "ud-phone-input", inputs: ["controlName", "label", "placeholder", "disabled", "hint", "size"] }, { kind: "component", type: UdButtonComponent, selector: "ud-button", inputs: ["variant", "color", "size", "type", "icon", "iconPosition", "iconFontSet", "loading", "disabled", "fullWidth"] }] });
2510
2547
  }
2511
2548
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: ModalComponent, decorators: [{
2512
2549
  type: Component,
@@ -2532,7 +2569,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImpo
2532
2569
  TimePickerComponent,
2533
2570
  PhoneInputComponent,
2534
2571
  UdButtonComponent,
2535
- ], template: "<div class=\"ud-modal-shell\">\n <header class=\"ud-modal-header\">\n @if (eyebrow) {\n <p class=\"ud-modal-eyebrow\">{{ eyebrow | translate | capitalize }}</p>\n }\n @if (title) {\n <h3 class=\"ud-modal-title\">{{ title | translate | capitalize }}</h3>\n }\n @if (lede) {\n <p class=\"ud-modal-lede\">{{ lede | translate | capitalize }}</p>\n }\n </header>\n\n <div class=\"ud-modal-content\" mat-dialog-content>\n @if (pictureUrls && currentPictureIndex != undefined) {\n <div class=\"image-container\">\n <mat-icon class=\"arrow-picture-icon\" (click)=\"previous()\">keyboard_arrow_left</mat-icon>\n <img [src]=\"pictureUrls[currentPictureIndex]\" alt=\"Picture\" />\n <mat-icon class=\"arrow-picture-icon\" (click)=\"next()\">keyboard_arrow_right</mat-icon>\n </div>\n <div class=\"image-footer\">\n {{ currentPictureIndex + 1 }} {{ 'names.of' | translate }} {{ pictureUrls.length }}\n </div>\n } @else if (form) {\n <form [formGroup]=\"form\">\n <div class=\"modal-form-fields\">\n @for (formType of data.forms; track formType) {\n @switch (formType.type) {\n @case (modalInputType.INPUT) {\n <ud-text-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [type]=\"formType.inputType ?? 'text'\"\n [placeholder]=\"formType.placeholder ?? ''\" />\n }\n @case (modalInputType.PHONE) {\n <ud-phone-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\" />\n }\n @case (modalInputType.TEXT_AREA) {\n <ud-textarea\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\"\n [minRows]=\"formType.minRows ?? 3\"\n [maxRows]=\"formType.maxRows ?? 6\" />\n }\n @case (modalInputType.OPTIONS) {\n <ud-multi-select\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [options]=\"formType.availableOptions\"\n [multiple]=\"false\" />\n }\n @case (modalInputType.MULTI_SELECT) {\n <ud-multi-select\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [options]=\"formType.availableOptions\"\n [multiple]=\"true\" />\n }\n @case (modalInputType.AUTOCOMPLETE) {\n <ud-autocomplete\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\"\n [options]=\"formType.availableOptions\" />\n }\n @case (modalInputType.DATERANGE) {\n <ud-date-range-input\n [startControlName]=\"'start' + formType.property\"\n [endControlName]=\"'end' + formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\" />\n }\n @case (modalInputType.DATETIME) {\n <ud-date-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [min]=\"formType.min\"\n [max]=\"formType.max\" />\n }\n @case (modalInputType.TIME) {\n <ud-time-picker\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [intervalMinutes]=\"formType.intervalMinutes ?? 30\"\n [options]=\"formType.availableOptions\" />\n }\n @case (modalInputType.CHIPS) {\n <mat-form-field appearance=\"outline\">\n <mat-label>\n {{ formType.title | translate | singular | capitalize }}\n </mat-label>\n <mat-chip-grid #chipGrid [formControlName]=\"formType.property\">\n @for (option of formType.availableOptions; track option) {\n <mat-chip-row (removed)=\"formType.removeOption(option)\">\n {{ option }}\n <button matChipRemove>\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip-row>\n }\n </mat-chip-grid>\n <input\n placeholder=\"New option...\"\n [matChipInputFor]=\"chipGrid\"\n (matChipInputTokenEnd)=\"formType.addOption($event)\" />\n </mat-form-field>\n }\n }\n }\n </div>\n </form>\n } @else if (bodyText) {\n <p class=\"ud-modal-body-text\">{{ bodyText }}</p>\n } @else {\n <ng-content></ng-content>\n }\n </div>\n\n @if (showFooter) {\n <footer class=\"ud-modal-footer\">\n @if (showClose) {\n <ud-button variant=\"stroked\" color=\"secondary\" (click)=\"close()\">\n {{ (cancelLabel ?? 'actions.close') | translate | capitalize }}\n </ud-button>\n }\n <ud-button\n [disabled]=\"!!(form && form.invalid) || confirmDisabled\"\n (click)=\"onAction()\">\n {{ (confirmLabel ?? (form ? 'actions.save' : 'actions.ok')) | translate | capitalize }}\n </ud-button>\n </footer>\n }\n</div>\n", styles: [".ud-modal-shell{display:flex;flex-direction:column;overflow:hidden;min-width:360px;width:100%}.ud-modal-header{position:relative;padding:28px 28px 18px;overflow:hidden}.ud-modal-header:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at 20% 0%,rgba(27,37,53,.1),transparent 60%),radial-gradient(circle at 85% 10%,rgba(58,74,102,.07),transparent 55%);pointer-events:none}.ud-modal-eyebrow{margin:0 0 6px;font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#1b2535}.ud-modal-title{margin:0;font-size:22px;line-height:1.2;font-weight:600;color:#2a3548;letter-spacing:-.01em}.ud-modal-lede{margin:8px 0 0;color:#6b7585;font-size:13.5px;line-height:1.5;max-width:40ch}.ud-modal-content{padding:4px 28px 12px;max-height:60vh;overflow-y:auto}.ud-modal-content.mat-mdc-dialog-content{padding:4px 28px 12px;margin:0;max-height:60vh}.modal-form-fields{display:flex;flex-direction:column;gap:4px;padding-top:8px}ud-text-input,ud-textarea,ud-multi-select,ud-autocomplete,ud-date-input,ud-date-range-input,ud-time-picker,ud-phone-input,mat-form-field{display:block;width:100%}.ud-modal-body-text{margin:4px 0;font-size:14px;color:#4a5568;line-height:1.55}.image-container{display:flex;justify-content:space-between;align-items:center}.image-container .arrow-picture-icon{display:flex;justify-content:center;align-items:center;padding:1rem;font-size:2rem;cursor:pointer;background:#1b2535;color:#fff;border-radius:8px;transition:background .15s ease}.image-container .arrow-picture-icon:hover{background:#253347}.image-container img{width:100%;max-width:460px;border-radius:8px}.image-footer{display:flex;justify-content:center;align-items:center;padding:8px 0 0;font-size:13px;color:#6b7585}.ud-modal-footer{display:flex;justify-content:flex-end;align-items:center;gap:10px;padding:16px 28px 24px;border-top:1px solid #e2e5ea;background:#fbfbfd}\n"] }]
2572
+ ], template: "<div class=\"ud-modal-shell\">\n <header class=\"ud-modal-header\">\n @if (eyebrow) {\n <p class=\"ud-modal-eyebrow\">{{ eyebrow | translate | capitalize }}</p>\n }\n @if (title) {\n <h3 class=\"ud-modal-title\">{{ title | translate | capitalize }}</h3>\n }\n @if (lede) {\n <p class=\"ud-modal-lede\">{{ lede | translate | capitalize }}</p>\n }\n </header>\n\n <div class=\"ud-modal-content\" mat-dialog-content>\n @if (pictureUrls && currentPictureIndex != undefined) {\n <div class=\"image-container\">\n <mat-icon class=\"arrow-picture-icon\" (click)=\"previous()\">keyboard_arrow_left</mat-icon>\n <img [src]=\"pictureUrls[currentPictureIndex]\" alt=\"Picture\" />\n <mat-icon class=\"arrow-picture-icon\" (click)=\"next()\">keyboard_arrow_right</mat-icon>\n </div>\n <div class=\"image-footer\">\n {{ currentPictureIndex + 1 }} {{ 'names.of' | translate }} {{ pictureUrls.length }}\n </div>\n } @else if (form) {\n <form [formGroup]=\"form\">\n <div class=\"modal-form-fields\">\n @for (formType of data.forms; track formType) {\n @switch (formType.type) {\n @case (modalInputType.INPUT) {\n <ud-text-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [type]=\"formType.inputType ?? 'text'\"\n [placeholder]=\"formType.placeholder ?? ''\" />\n }\n @case (modalInputType.PHONE) {\n <ud-phone-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\" />\n }\n @case (modalInputType.TEXT_AREA) {\n <ud-textarea\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\"\n [minRows]=\"formType.minRows ?? 3\"\n [maxRows]=\"formType.maxRows ?? 6\" />\n }\n @case (modalInputType.OPTIONS) {\n <ud-multi-select\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [options]=\"formType.availableOptions\"\n [multiple]=\"false\" />\n }\n @case (modalInputType.MULTI_SELECT) {\n <ud-multi-select\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [options]=\"formType.availableOptions\"\n [multiple]=\"true\" />\n }\n @case (modalInputType.AUTOCOMPLETE) {\n <ud-autocomplete\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [placeholder]=\"formType.placeholder ?? ''\"\n [options]=\"formType.availableOptions\" />\n }\n @case (modalInputType.DATERANGE) {\n <ud-date-range-input\n [startControlName]=\"'start' + formType.property\"\n [endControlName]=\"'end' + formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\" />\n }\n @case (modalInputType.DATETIME) {\n <ud-date-input\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [min]=\"formType.min\"\n [max]=\"formType.max\" />\n }\n @case (modalInputType.TIME) {\n <ud-time-picker\n [controlName]=\"formType.property\"\n [label]=\"formType.title | translate | singular | capitalize\"\n [intervalMinutes]=\"formType.intervalMinutes ?? 30\"\n [options]=\"formType.availableOptions\" />\n }\n @case (modalInputType.CHIPS) {\n <mat-form-field appearance=\"outline\">\n <mat-label>\n {{ formType.title | translate | singular | capitalize }}\n </mat-label>\n <mat-chip-grid #chipGrid [formControlName]=\"formType.property\">\n @for (option of formType.availableOptions; track option) {\n <mat-chip-row (removed)=\"formType.removeOption(option)\">\n {{ option }}\n <button matChipRemove>\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip-row>\n }\n </mat-chip-grid>\n <input\n placeholder=\"New option...\"\n [matChipInputFor]=\"chipGrid\"\n (matChipInputTokenEnd)=\"formType.addOption($event)\" />\n </mat-form-field>\n }\n }\n }\n </div>\n </form>\n } @else if (bodyText) {\n <p class=\"ud-modal-body-text\">{{ bodyText }}</p>\n } @else {\n <ng-content></ng-content>\n }\n </div>\n\n @if (showFooter) {\n <footer class=\"ud-modal-footer\">\n @if (data.delete) {\n <ud-button variant=\"stroked\" color=\"danger\" class=\"ud-modal-footer__delete\" (click)=\"onDelete()\">\n {{ data.deleteLabel ?? 'Delete' }}\n </ud-button>\n }\n @if (showClose) {\n <ud-button variant=\"stroked\" color=\"secondary\" (click)=\"close()\">\n {{ (cancelLabel ?? 'actions.close') | translate | capitalize }}\n </ud-button>\n }\n <ud-button\n [disabled]=\"!!(form && form.invalid) || confirmDisabled\"\n (click)=\"onAction()\">\n {{ (confirmLabel ?? (form ? 'actions.save' : 'actions.ok')) | translate | capitalize }}\n </ud-button>\n </footer>\n }\n</div>\n", styles: [".ud-modal-shell{display:flex;flex-direction:column;overflow:hidden;min-width:360px;width:100%}.ud-modal-header{position:relative;padding:28px 28px 18px;overflow:hidden}.ud-modal-header:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at 20% 0%,rgba(27,37,53,.1),transparent 60%),radial-gradient(circle at 85% 10%,rgba(58,74,102,.07),transparent 55%);pointer-events:none}.ud-modal-eyebrow{margin:0 0 6px;font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#1b2535}.ud-modal-title{margin:0;font-size:22px;line-height:1.2;font-weight:600;color:#2a3548;letter-spacing:-.01em}.ud-modal-lede{margin:8px 0 0;color:#6b7585;font-size:13.5px;line-height:1.5;max-width:40ch}.ud-modal-content{padding:4px 28px 12px;max-height:60vh;overflow-y:auto}.ud-modal-content.mat-mdc-dialog-content{padding:4px 28px 12px;margin:0;max-height:60vh}.modal-form-fields{display:flex;flex-direction:column;gap:4px;padding-top:8px}ud-text-input,ud-textarea,ud-multi-select,ud-autocomplete,ud-date-input,ud-date-range-input,ud-time-picker,ud-phone-input,mat-form-field{display:block;width:100%}.ud-modal-body-text{margin:4px 0;font-size:14px;color:#4a5568;line-height:1.55}.image-container{display:flex;justify-content:space-between;align-items:center}.image-container .arrow-picture-icon{display:flex;justify-content:center;align-items:center;padding:1rem;font-size:2rem;cursor:pointer;background:#1b2535;color:#fff;border-radius:8px;transition:background .15s ease}.image-container .arrow-picture-icon:hover{background:#253347}.image-container img{width:100%;max-width:460px;border-radius:8px}.image-footer{display:flex;justify-content:center;align-items:center;padding:8px 0 0;font-size:13px;color:#6b7585}.ud-modal-footer{display:flex;justify-content:flex-end;align-items:center;gap:10px;padding:16px 28px 24px;border-top:1px solid #e2e5ea;background:#fbfbfd}.ud-modal-footer__delete{margin-right:auto}\n"] }]
2536
2573
  }], ctorParameters: () => [{ type: undefined, decorators: [{
2537
2574
  type: Optional
2538
2575
  }, {
@@ -2671,7 +2708,7 @@ class FileInputComponent {
2671
2708
  this.dialog.open(ModalComponent, dialogConfig);
2672
2709
  }
2673
2710
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: FileInputComponent, deps: [{ token: i1$4.MatDialog }], target: i0.ɵɵFactoryTarget.Component });
2674
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: FileInputComponent, isStandalone: true, selector: "ud-file-input", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: false, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: false, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: false, isRequired: false, transformFunction: null }, initialFiles: { classPropertyName: "initialFiles", publicName: "initialFiles", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filesSelected: "filesSelected" }, ngImport: i0, template: "<div class=\"mt-3\">\n <ngx-file-drop\n dropZoneLabel=\"Drop files here\"\n [accept]=\"accept\"\n [multiple]=\"multiple\"\n [directory]=\"false\"\n (onFileDrop)=\"dropped($event)\">\n <ng-template\n ngx-file-drop-content-tmp\n let-openFileSelector=\"openFileSelector\">\n <div class=\"drop-stack\">\n <ud-button color=\"primary\" type=\"button\" [icon]=\"icon\" (click)=\"openFileSelector()\">\n <span class=\"text-wrap\">{{ label | translate | capitalize }}</span>\n </ud-button>\n <div class=\"drop-hint\">\n <mat-icon class=\"drop-hint__icon\" fontSet=\"material-icons-outlined\"\n >cloud_upload</mat-icon\n >\n <span class=\"drop-hint__text\">{{\n 'fileInput.orDropHere' | translate\n }}</span>\n </div>\n </div>\n </ng-template>\n </ngx-file-drop>\n <div class=\"mt-3\">\n @for (image of previewUrls; track $index) {\n <div\n class=\"d-flex justify-content-between align-items-center mb-2 p-1 file\">\n @if (image) {\n <div class=\"d-flex\">\n @if (image.substring(0, 30).includes('pdf')) {\n <iframe\n [src]=\"image | safe\"\n class=\"mr-3 border\"\n allowFullscreen></iframe>\n } @else {\n <img\n [src]=\"image\"\n alt=\"\"\n class=\"mr-3 border\"\n (click)=\"openImageDialog($index)\" />\n }\n </div>\n }\n <button mat-icon-button type=\"button\" (click)=\"removeFile($index)\">\n <mat-icon fontSet=\"material-icons-outlined\"> cancel</mat-icon>\n </button>\n </div>\n }\n </div>\n</div>\n", styles: [".cancel{padding:0;min-width:fit-content;height:fit-content;margin-left:-13px;margin-top:-12px}.cancel ::ng-deep .mat-button-wrapper{display:flex}.cancel ::ng-deep .mat-button-wrapper mat-icon{background:#fff;border-radius:20px}img,iframe{width:65px;height:65px;object-fit:cover;border-radius:10px;cursor:pointer}img:hover,iframe:hover{opacity:.6}.file{border:1px dashed #373f4c;border-radius:10px}.drop-stack{display:flex;flex-direction:column;align-items:flex-start}.drop-hint{display:flex;align-items:center;gap:8px;margin-top:10px;padding-left:2px;color:#373f4c8c;font-size:.78rem;font-weight:500;letter-spacing:.02em;-webkit-user-select:none;user-select:none;animation:drop-hint-in .42s cubic-bezier(.2,.8,.2,1) both}.drop-hint__icon{font-size:16px;width:16px;height:16px;color:#1b2535;opacity:.85;transition:transform .26s ease,opacity .26s ease}.drop-hint__text{line-height:1;font-style:italic}.drop-hint:hover .drop-hint__icon{transform:translateY(-1px);opacity:1}@keyframes drop-hint-in{0%{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: NgxFileDropModule }, { kind: "component", type: i2$6.NgxFileDropComponent, selector: "ngx-file-drop", inputs: ["accept", "directory", "multiple", "dropZoneLabel", "dropZoneClassName", "useDragEnter", "contentClassName", "showBrowseBtn", "browseBtnClassName", "browseBtnLabel", "disabled"], outputs: ["onFileDrop", "onFileOver", "onFileLeave"] }, { kind: "directive", type: i2$6.NgxFileDropContentTemplateDirective, selector: "[ngx-file-drop-content-tmp]" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: CapitalizePipe, name: "capitalize" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "pipe", type: SafePipe, name: "safe" }, { kind: "component", type: UdButtonComponent, selector: "ud-button", inputs: ["variant", "color", "size", "type", "icon", "iconPosition", "iconFontSet", "loading", "disabled", "fullWidth"] }] });
2711
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: FileInputComponent, isStandalone: true, selector: "ud-file-input", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: false, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: false, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: false, isRequired: false, transformFunction: null }, initialFiles: { classPropertyName: "initialFiles", publicName: "initialFiles", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filesSelected: "filesSelected" }, ngImport: i0, template: "<div class=\"mt-3\">\n <ngx-file-drop\n dropZoneLabel=\"Drop files here\"\n [accept]=\"accept\"\n [multiple]=\"multiple\"\n [directory]=\"false\"\n (onFileDrop)=\"dropped($event)\">\n <ng-template\n ngx-file-drop-content-tmp\n let-openFileSelector=\"openFileSelector\">\n <div class=\"drop-stack\">\n <ud-button color=\"primary\" type=\"button\" [icon]=\"icon\" (click)=\"openFileSelector()\">\n <span class=\"text-wrap\">{{ label | translate | capitalize }}</span>\n </ud-button>\n <div class=\"drop-hint\">\n <mat-icon class=\"drop-hint__icon\" fontSet=\"material-icons-outlined\"\n >cloud_upload</mat-icon\n >\n <span class=\"drop-hint__text\">{{\n 'fileInput.orDropHere' | translate\n }}</span>\n </div>\n </div>\n </ng-template>\n </ngx-file-drop>\n <div class=\"mt-3\">\n @for (image of previewUrls; track $index) {\n <div\n class=\"d-flex justify-content-between align-items-center mb-2 p-1 file\">\n @if (image) {\n <div class=\"d-flex\">\n @if (image.substring(0, 30).includes('pdf')) {\n <iframe\n [src]=\"image | safe\"\n class=\"mr-3 border\"\n allowFullscreen></iframe>\n } @else {\n <img\n [src]=\"image\"\n alt=\"\"\n class=\"mr-3 border\"\n (click)=\"openImageDialog($index)\" />\n }\n </div>\n }\n <button mat-icon-button type=\"button\" (click)=\"removeFile($index)\">\n <mat-icon fontSet=\"material-icons-outlined\"> cancel</mat-icon>\n </button>\n </div>\n }\n </div>\n</div>\n", styles: [".cancel{padding:0;min-width:fit-content;height:fit-content;margin-left:-13px;margin-top:-12px}.cancel ::ng-deep .mat-button-wrapper{display:flex}.cancel ::ng-deep .mat-button-wrapper mat-icon{background:#fff;border-radius:20px}img,iframe{width:65px;height:65px;object-fit:cover;border-radius:10px;cursor:pointer}img:hover,iframe:hover{opacity:.6}.file{border:1px dashed #373f4c;border-radius:10px}.drop-stack{display:flex;flex-direction:column;align-items:flex-start}.drop-hint{display:flex;align-items:center;gap:8px;margin-top:10px;padding-left:2px;color:#373f4c8c;font-size:.78rem;font-weight:500;letter-spacing:.02em;-webkit-user-select:none;user-select:none;animation:drop-hint-in .42s cubic-bezier(.2,.8,.2,1) both}.drop-hint__icon{font-size:16px;width:16px;height:16px;color:#1b2535;opacity:.85;transition:transform .26s ease,opacity .26s ease}.drop-hint__text{line-height:1;font-style:italic}.drop-hint:hover .drop-hint__icon{transform:translateY(-1px);opacity:1}@keyframes drop-hint-in{0%{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: NgxFileDropModule }, { kind: "component", type: i2$5.NgxFileDropComponent, selector: "ngx-file-drop", inputs: ["accept", "directory", "multiple", "dropZoneLabel", "dropZoneClassName", "useDragEnter", "contentClassName", "showBrowseBtn", "browseBtnClassName", "browseBtnLabel", "disabled"], outputs: ["onFileDrop", "onFileOver", "onFileLeave"] }, { kind: "directive", type: i2$5.NgxFileDropContentTemplateDirective, selector: "[ngx-file-drop-content-tmp]" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: CapitalizePipe, name: "capitalize" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "pipe", type: SafePipe, name: "safe" }, { kind: "component", type: UdButtonComponent, selector: "ud-button", inputs: ["variant", "color", "size", "type", "icon", "iconPosition", "iconFontSet", "loading", "disabled", "fullWidth"] }] });
2675
2712
  }
2676
2713
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: FileInputComponent, decorators: [{
2677
2714
  type: Component,
@@ -2938,11 +2975,11 @@ class UdStepperComponent {
2938
2975
  this._stepper.reset();
2939
2976
  }
2940
2977
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: UdStepperComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
2941
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: UdStepperComponent, isStandalone: true, selector: "ud-stepper", inputs: { steps: "steps", linear: "linear", selectedIndex: "selectedIndex" }, outputs: { selectedIndexChange: "selectedIndexChange", stepChange: "stepChange" }, queries: [{ propertyName: "_contentTemplates", predicate: UdStepContentDirective }], viewQueries: [{ propertyName: "_stepper", first: true, predicate: ["stepper"], descendants: true }], ngImport: i0, template: "<mat-stepper\n class=\"ud-stepper\"\n [linear]=\"linear\"\n [selectedIndex]=\"selectedIndex\"\n (selectionChange)=\"onSelectionChange($event)\"\n #stepper>\n @for (step of steps; track step.label; let i = $index) {\n <mat-step [label]=\"step.label\" [completed]=\"step.completed ?? false\">\n @if (contentList[i]) {\n <ng-container *ngTemplateOutlet=\"contentList[i].templateRef\" />\n }\n </mat-step>\n }\n</mat-stepper>\n", styles: [":host{display:block}::ng-deep .ud-stepper{background:transparent;font-family:DM Sans,system-ui,sans-serif}::ng-deep .ud-stepper .mat-step-header{border-radius:var(--eu-radius-sm, 6px);transition:background .18s ease;padding:.75rem 1rem}::ng-deep .ud-stepper .mat-step-header:hover{background:var(--eu-navy-dim, rgba(27, 37, 53, .08))!important}::ng-deep .ud-stepper .mat-step-header:focus-visible{outline:2px solid var(--eu-navy, #1b2535);outline-offset:2px}::ng-deep .ud-stepper .mat-step-header .mat-step-header-ripple,::ng-deep .ud-stepper .mat-step-header .mat-ripple{border-radius:var(--eu-radius-sm, 6px)}::ng-deep .ud-stepper .mat-step-icon{background:var(--eu-bg, #f4f5f7);color:var(--eu-muted, #6b7585);box-shadow:inset 0 0 0 2px var(--eu-border-mid, #c9cdd6);transition:background .18s ease,color .18s ease,box-shadow .18s ease;font-family:DM Sans,system-ui,sans-serif;font-weight:600}::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-selected{background:var(--eu-navy, #1b2535)!important;color:#fff!important;box-shadow:none!important}::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-state-done,::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-state-edit{background:var(--eu-navy, #1b2535)!important;color:#fff!important;box-shadow:none!important}::ng-deep .ud-stepper .mat-step-label{color:var(--eu-muted, #6b7585);font-family:DM Sans,system-ui,sans-serif;font-size:.875rem;font-weight:400;transition:color .18s ease,font-weight .18s ease}::ng-deep .ud-stepper .mat-step-label.mat-step-label-active{color:var(--eu-text, #1b2535)}::ng-deep .ud-stepper .mat-step-label.mat-step-label-selected{color:var(--eu-navy, #1b2535);font-weight:600}::ng-deep .ud-stepper .mat-stepper-horizontal-line{border-top-color:var(--eu-border, #e2e5ea)!important;margin:0!important}::ng-deep .ud-stepper .mat-stepper-vertical-line:before{border-left-color:var(--eu-border, #e2e5ea)!important}::ng-deep .ud-stepper .mat-horizontal-content-container{padding:1.5rem 0 0}::ng-deep .ud-stepper .mat-vertical-content-container{margin-left:36px;border-left:2px solid var(--eu-border, #e2e5ea);padding-left:1.25rem}::ng-deep .ud-stepper .mat-vertical-content{padding:0 0 1.5rem}::ng-deep .ud-stepper .mat-horizontal-stepper-content:not(.mat-horizontal-stepper-content-inactive){animation:ud-stepper-fade-in .2s ease}@keyframes ud-stepper-fade-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatStepperModule }, { kind: "component", type: i1$6.MatStep, selector: "mat-step", inputs: ["color"], exportAs: ["matStep"] }, { kind: "component", type: i1$6.MatStepper, selector: "mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]", inputs: ["disableRipple", "color", "labelPosition", "headerPosition", "animationDuration"], outputs: ["animationDone"], exportAs: ["matStepper", "matVerticalStepper", "matHorizontalStepper"] }, { kind: "ngmodule", type: MatIconModule }] });
2978
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: UdStepperComponent, isStandalone: true, selector: "ud-stepper", inputs: { steps: "steps", linear: "linear", selectedIndex: "selectedIndex" }, outputs: { selectedIndexChange: "selectedIndexChange", stepChange: "stepChange" }, queries: [{ propertyName: "_contentTemplates", predicate: UdStepContentDirective }], viewQueries: [{ propertyName: "_stepper", first: true, predicate: ["stepper"], descendants: true }], ngImport: i0, template: "<mat-stepper\n class=\"ud-stepper\"\n [linear]=\"linear\"\n [selectedIndex]=\"selectedIndex\"\n (selectionChange)=\"onSelectionChange($event)\"\n #stepper>\n @for (step of steps; track step.label; let i = $index) {\n <mat-step [label]=\"step.label\" [completed]=\"step.completed ?? false\">\n <ng-template matStepContent>\n @if (contentList[i]) {\n <ng-container *ngTemplateOutlet=\"contentList[i].templateRef\" />\n }\n </ng-template>\n </mat-step>\n }\n</mat-stepper>\n", styles: [":host{display:block}::ng-deep .ud-stepper{background:transparent;font-family:DM Sans,system-ui,sans-serif}::ng-deep .ud-stepper .mat-step-header{border-radius:var(--eu-radius-sm, 6px);transition:background .18s ease;padding:.75rem 1rem}::ng-deep .ud-stepper .mat-step-header:hover{background:var(--eu-navy-dim, rgba(27, 37, 53, .08))!important}::ng-deep .ud-stepper .mat-step-header:focus-visible{outline:2px solid var(--eu-navy, #1b2535);outline-offset:2px}::ng-deep .ud-stepper .mat-step-header .mat-step-header-ripple,::ng-deep .ud-stepper .mat-step-header .mat-ripple{border-radius:var(--eu-radius-sm, 6px)}::ng-deep .ud-stepper .mat-step-icon{background:var(--eu-bg, #f4f5f7);color:var(--eu-muted, #6b7585);box-shadow:inset 0 0 0 2px var(--eu-border-mid, #c9cdd6);transition:background .18s ease,color .18s ease,box-shadow .18s ease;font-family:DM Sans,system-ui,sans-serif;font-weight:600}::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-selected{background:var(--eu-navy, #1b2535)!important;color:#fff!important;box-shadow:none!important}::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-state-done,::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-state-edit{background:var(--eu-navy, #1b2535)!important;color:#fff!important;box-shadow:none!important}::ng-deep .ud-stepper .mat-step-label{color:var(--eu-muted, #6b7585);font-family:DM Sans,system-ui,sans-serif;font-size:.875rem;font-weight:400;transition:color .18s ease,font-weight .18s ease}::ng-deep .ud-stepper .mat-step-label.mat-step-label-active{color:var(--eu-text, #1b2535)}::ng-deep .ud-stepper .mat-step-label.mat-step-label-selected{color:var(--eu-navy, #1b2535);font-weight:600}::ng-deep .ud-stepper .mat-stepper-horizontal-line{border-top-color:var(--eu-border, #e2e5ea)!important;margin:0!important}::ng-deep .ud-stepper .mat-stepper-vertical-line:before{border-left-color:var(--eu-border, #e2e5ea)!important}::ng-deep .ud-stepper .mat-horizontal-content-container{padding:1.5rem 2rem 2rem}::ng-deep .ud-stepper .mat-vertical-content-container{margin-left:36px;border-left:2px solid var(--eu-border, #e2e5ea);padding-left:1.25rem}::ng-deep .ud-stepper .mat-vertical-content{padding:1rem 0 1.5rem}::ng-deep .ud-stepper .mat-horizontal-stepper-content:not(.mat-horizontal-stepper-content-inactive){animation:ud-stepper-fade-in .2s ease}@keyframes ud-stepper-fade-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatStepperModule }, { kind: "component", type: i1$6.MatStep, selector: "mat-step", inputs: ["color"], exportAs: ["matStep"] }, { kind: "component", type: i1$6.MatStepper, selector: "mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]", inputs: ["disableRipple", "color", "labelPosition", "headerPosition", "animationDuration"], outputs: ["animationDone"], exportAs: ["matStepper", "matVerticalStepper", "matHorizontalStepper"] }, { kind: "directive", type: i1$6.MatStepContent, selector: "ng-template[matStepContent]" }, { kind: "ngmodule", type: MatIconModule }] });
2942
2979
  }
2943
2980
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: UdStepperComponent, decorators: [{
2944
2981
  type: Component,
2945
- args: [{ selector: 'ud-stepper', standalone: true, imports: [NgTemplateOutlet, MatStepperModule, MatIconModule], template: "<mat-stepper\n class=\"ud-stepper\"\n [linear]=\"linear\"\n [selectedIndex]=\"selectedIndex\"\n (selectionChange)=\"onSelectionChange($event)\"\n #stepper>\n @for (step of steps; track step.label; let i = $index) {\n <mat-step [label]=\"step.label\" [completed]=\"step.completed ?? false\">\n @if (contentList[i]) {\n <ng-container *ngTemplateOutlet=\"contentList[i].templateRef\" />\n }\n </mat-step>\n }\n</mat-stepper>\n", styles: [":host{display:block}::ng-deep .ud-stepper{background:transparent;font-family:DM Sans,system-ui,sans-serif}::ng-deep .ud-stepper .mat-step-header{border-radius:var(--eu-radius-sm, 6px);transition:background .18s ease;padding:.75rem 1rem}::ng-deep .ud-stepper .mat-step-header:hover{background:var(--eu-navy-dim, rgba(27, 37, 53, .08))!important}::ng-deep .ud-stepper .mat-step-header:focus-visible{outline:2px solid var(--eu-navy, #1b2535);outline-offset:2px}::ng-deep .ud-stepper .mat-step-header .mat-step-header-ripple,::ng-deep .ud-stepper .mat-step-header .mat-ripple{border-radius:var(--eu-radius-sm, 6px)}::ng-deep .ud-stepper .mat-step-icon{background:var(--eu-bg, #f4f5f7);color:var(--eu-muted, #6b7585);box-shadow:inset 0 0 0 2px var(--eu-border-mid, #c9cdd6);transition:background .18s ease,color .18s ease,box-shadow .18s ease;font-family:DM Sans,system-ui,sans-serif;font-weight:600}::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-selected{background:var(--eu-navy, #1b2535)!important;color:#fff!important;box-shadow:none!important}::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-state-done,::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-state-edit{background:var(--eu-navy, #1b2535)!important;color:#fff!important;box-shadow:none!important}::ng-deep .ud-stepper .mat-step-label{color:var(--eu-muted, #6b7585);font-family:DM Sans,system-ui,sans-serif;font-size:.875rem;font-weight:400;transition:color .18s ease,font-weight .18s ease}::ng-deep .ud-stepper .mat-step-label.mat-step-label-active{color:var(--eu-text, #1b2535)}::ng-deep .ud-stepper .mat-step-label.mat-step-label-selected{color:var(--eu-navy, #1b2535);font-weight:600}::ng-deep .ud-stepper .mat-stepper-horizontal-line{border-top-color:var(--eu-border, #e2e5ea)!important;margin:0!important}::ng-deep .ud-stepper .mat-stepper-vertical-line:before{border-left-color:var(--eu-border, #e2e5ea)!important}::ng-deep .ud-stepper .mat-horizontal-content-container{padding:1.5rem 0 0}::ng-deep .ud-stepper .mat-vertical-content-container{margin-left:36px;border-left:2px solid var(--eu-border, #e2e5ea);padding-left:1.25rem}::ng-deep .ud-stepper .mat-vertical-content{padding:0 0 1.5rem}::ng-deep .ud-stepper .mat-horizontal-stepper-content:not(.mat-horizontal-stepper-content-inactive){animation:ud-stepper-fade-in .2s ease}@keyframes ud-stepper-fade-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}\n"] }]
2982
+ args: [{ selector: 'ud-stepper', standalone: true, imports: [NgTemplateOutlet, MatStepperModule, MatIconModule], template: "<mat-stepper\n class=\"ud-stepper\"\n [linear]=\"linear\"\n [selectedIndex]=\"selectedIndex\"\n (selectionChange)=\"onSelectionChange($event)\"\n #stepper>\n @for (step of steps; track step.label; let i = $index) {\n <mat-step [label]=\"step.label\" [completed]=\"step.completed ?? false\">\n <ng-template matStepContent>\n @if (contentList[i]) {\n <ng-container *ngTemplateOutlet=\"contentList[i].templateRef\" />\n }\n </ng-template>\n </mat-step>\n }\n</mat-stepper>\n", styles: [":host{display:block}::ng-deep .ud-stepper{background:transparent;font-family:DM Sans,system-ui,sans-serif}::ng-deep .ud-stepper .mat-step-header{border-radius:var(--eu-radius-sm, 6px);transition:background .18s ease;padding:.75rem 1rem}::ng-deep .ud-stepper .mat-step-header:hover{background:var(--eu-navy-dim, rgba(27, 37, 53, .08))!important}::ng-deep .ud-stepper .mat-step-header:focus-visible{outline:2px solid var(--eu-navy, #1b2535);outline-offset:2px}::ng-deep .ud-stepper .mat-step-header .mat-step-header-ripple,::ng-deep .ud-stepper .mat-step-header .mat-ripple{border-radius:var(--eu-radius-sm, 6px)}::ng-deep .ud-stepper .mat-step-icon{background:var(--eu-bg, #f4f5f7);color:var(--eu-muted, #6b7585);box-shadow:inset 0 0 0 2px var(--eu-border-mid, #c9cdd6);transition:background .18s ease,color .18s ease,box-shadow .18s ease;font-family:DM Sans,system-ui,sans-serif;font-weight:600}::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-selected{background:var(--eu-navy, #1b2535)!important;color:#fff!important;box-shadow:none!important}::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-state-done,::ng-deep .ud-stepper .mat-step-icon.mat-step-icon-state-edit{background:var(--eu-navy, #1b2535)!important;color:#fff!important;box-shadow:none!important}::ng-deep .ud-stepper .mat-step-label{color:var(--eu-muted, #6b7585);font-family:DM Sans,system-ui,sans-serif;font-size:.875rem;font-weight:400;transition:color .18s ease,font-weight .18s ease}::ng-deep .ud-stepper .mat-step-label.mat-step-label-active{color:var(--eu-text, #1b2535)}::ng-deep .ud-stepper .mat-step-label.mat-step-label-selected{color:var(--eu-navy, #1b2535);font-weight:600}::ng-deep .ud-stepper .mat-stepper-horizontal-line{border-top-color:var(--eu-border, #e2e5ea)!important;margin:0!important}::ng-deep .ud-stepper .mat-stepper-vertical-line:before{border-left-color:var(--eu-border, #e2e5ea)!important}::ng-deep .ud-stepper .mat-horizontal-content-container{padding:1.5rem 2rem 2rem}::ng-deep .ud-stepper .mat-vertical-content-container{margin-left:36px;border-left:2px solid var(--eu-border, #e2e5ea);padding-left:1.25rem}::ng-deep .ud-stepper .mat-vertical-content{padding:1rem 0 1.5rem}::ng-deep .ud-stepper .mat-horizontal-stepper-content:not(.mat-horizontal-stepper-content-inactive){animation:ud-stepper-fade-in .2s ease}@keyframes ud-stepper-fade-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}\n"] }]
2946
2983
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { steps: [{
2947
2984
  type: Input
2948
2985
  }], linear: [{
@@ -3120,6 +3157,497 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImpo
3120
3157
  type: Input
3121
3158
  }] } });
3122
3159
 
3160
+ class ChipInputComponent {
3161
+ controlName;
3162
+ label = '';
3163
+ placeholder = '';
3164
+ icon;
3165
+ iconFontSet = 'material-icons-outlined';
3166
+ disabled = false;
3167
+ hint = '';
3168
+ focused = false;
3169
+ separatorKeysCodes = [ENTER, COMMA];
3170
+ controlContainer = inject(ControlContainer);
3171
+ get control() {
3172
+ return this.controlContainer.control.get(this.controlName);
3173
+ }
3174
+ get chips() {
3175
+ return this.control.value ?? [];
3176
+ }
3177
+ ngOnChanges(changes) {
3178
+ if (changes['disabled']) {
3179
+ this.disabled ? this.control.disable() : this.control.enable();
3180
+ }
3181
+ }
3182
+ addChip(event) {
3183
+ const value = (event.value ?? '').trim();
3184
+ if (value) {
3185
+ this.control.setValue([...this.chips, value]);
3186
+ this.control.markAsDirty();
3187
+ }
3188
+ event.chipInput.clear();
3189
+ }
3190
+ removeChip(index) {
3191
+ const updated = this.chips.filter((_, i) => i !== index);
3192
+ this.control.setValue(updated);
3193
+ this.control.markAsDirty();
3194
+ }
3195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: ChipInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3196
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: ChipInputComponent, isStandalone: true, selector: "ud-chip-input", inputs: { controlName: "controlName", label: "label", placeholder: "placeholder", icon: "icon", iconFontSet: "iconFontSet", disabled: "disabled", hint: "hint" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"ud-chip-input\"\n [class.ud-chip-input--focused]=\"focused\"\n [class.ud-chip-input--disabled]=\"control.disabled\"\n [class.ud-chip-input--error]=\"control.invalid && control.touched\">\n @if (label) {\n <label class=\"ud-chip-input__label\">{{ label }}</label>\n }\n <div class=\"ud-chip-input__wrapper\">\n @if (icon) {\n <mat-icon class=\"ud-chip-input__icon\" [fontSet]=\"iconFontSet\">{{ icon }}</mat-icon>\n }\n <mat-chip-grid #chipGrid class=\"ud-chip-input__grid\" aria-label=\"Chip input\">\n @for (chip of chips; track chip; let i = $index) {\n <mat-chip-row class=\"ud-chip\" (removed)=\"removeChip(i)\">\n {{ chip }}\n <button matChipRemove aria-label=\"Remove chip\">\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip-row>\n }\n <input\n class=\"ud-chip-input__field\"\n [placeholder]=\"chips.length === 0 ? placeholder : ''\"\n [matChipInputFor]=\"chipGrid\"\n [matChipInputSeparatorKeyCodes]=\"separatorKeysCodes\"\n [disabled]=\"control.disabled\"\n (matChipInputTokenEnd)=\"addChip($event)\"\n (focus)=\"focused = true\"\n (blur)=\"focused = false\" />\n </mat-chip-grid>\n </div>\n @if (hint) {\n <span class=\"ud-chip-input__hint\">{{ hint }}</span>\n }\n</div>\n", styles: [":host{display:block;width:100%}.ud-chip-input{display:flex;flex-direction:column;gap:5px;width:100%}.ud-chip-input__label{font-family:DM Sans,system-ui,sans-serif;font-size:13px;font-weight:500;color:var(--eu-text, #2a3548);line-height:1}.ud-chip-input__wrapper{display:flex;align-items:flex-start;gap:8px;background:#f8fafc;border:1px solid var(--eu-border-mid, #d8dde6);border-radius:8px;padding:6px 12px;min-height:40px;transition:border-color .18s ease,box-shadow .18s ease,background .18s ease}.ud-chip-input--focused .ud-chip-input__wrapper{border-color:var(--eu-navy, #1b2535);box-shadow:0 0 0 3px #1b253514;background:#fff}.ud-chip-input--error .ud-chip-input__wrapper{border-color:#e53935;box-shadow:0 0 0 3px #e539351a}.ud-chip-input--error.ud-chip-input--focused .ud-chip-input__wrapper{border-color:#e53935}.ud-chip-input--disabled .ud-chip-input__wrapper{background:var(--eu-bg, #f4f5f7);border-color:var(--eu-border-light, #e8eaef);cursor:not-allowed;opacity:.6}.ud-chip-input__icon{flex-shrink:0;color:var(--eu-muted, #6b7585);font-size:18px;width:18px;height:18px;margin-top:3px;line-height:1;transition:color .18s ease}.ud-chip-input--focused .ud-chip-input__icon{color:var(--eu-navy, #1b2535)}.ud-chip-input__grid{flex:1;min-width:0}:host ::ng-deep .ud-chip-input__grid .mdc-evolution-chip-set__chips{display:flex;flex-wrap:wrap;align-items:center;gap:4px;margin:0;padding:0}.ud-chip-input__field{flex:1;min-width:80px;border:none;background:transparent;outline:none;font-family:DM Sans,system-ui,sans-serif;font-size:14px;color:var(--eu-text, #2a3548);line-height:1;padding:2px 0}.ud-chip-input__field::placeholder{color:var(--eu-muted, #9099a8)}.ud-chip-input__field:disabled{cursor:not-allowed;color:var(--eu-muted, #9099a8)}:host ::ng-deep mat-chip-row.ud-chip{--mdc-chip-container-color: rgba(27, 37, 53, .07);--mdc-chip-label-text-color: var(--eu-text, #2a3548);--mdc-chip-label-text-font: \"DM Sans\", system-ui, sans-serif;--mdc-chip-label-text-size: 12px;--mdc-chip-label-text-weight: 500;--mdc-chip-container-shape-radius: 20px;--mdc-chip-with-trailing-icon-trailing-icon-color: var(--eu-muted, #6b7585);--mdc-chip-elevated-container-color: rgba(27, 37, 53, .07);height:24px;border:none}:host ::ng-deep mat-chip-row.ud-chip .mdc-evolution-chip__action--trailing{padding:0 4px}:host ::ng-deep mat-chip-row.ud-chip .mdc-evolution-chip__action--trailing .mat-icon{font-size:14px;width:14px;height:14px;line-height:14px;color:var(--eu-muted, #6b7585);transition:color .15s ease}:host ::ng-deep mat-chip-row.ud-chip .mdc-evolution-chip__action--trailing:hover .mat-icon{color:var(--eu-navy, #1b2535)}.ud-chip-input__hint{font-family:DM Sans,system-ui,sans-serif;font-size:12px;color:var(--eu-muted, #6b7585);line-height:1.3}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "component", type: MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "directive", type: MatChipInput, selector: "input[matChipInputFor]", inputs: ["matChipInputFor", "matChipInputAddOnBlur", "matChipInputSeparatorKeyCodes", "placeholder", "id", "disabled"], outputs: ["matChipInputTokenEnd"], exportAs: ["matChipInput", "matChipInputFor"] }, { kind: "directive", type: MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], viewProviders: [{ provide: ControlContainer, useFactory: () => inject(ControlContainer, { skipSelf: true }) }] });
3197
+ }
3198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: ChipInputComponent, decorators: [{
3199
+ type: Component,
3200
+ args: [{ selector: 'ud-chip-input', standalone: true, imports: [ReactiveFormsModule, MatChipGrid, MatChipRow, MatChipInput, MatChipRemove, MatIcon], viewProviders: [{ provide: ControlContainer, useFactory: () => inject(ControlContainer, { skipSelf: true }) }], template: "<div\n class=\"ud-chip-input\"\n [class.ud-chip-input--focused]=\"focused\"\n [class.ud-chip-input--disabled]=\"control.disabled\"\n [class.ud-chip-input--error]=\"control.invalid && control.touched\">\n @if (label) {\n <label class=\"ud-chip-input__label\">{{ label }}</label>\n }\n <div class=\"ud-chip-input__wrapper\">\n @if (icon) {\n <mat-icon class=\"ud-chip-input__icon\" [fontSet]=\"iconFontSet\">{{ icon }}</mat-icon>\n }\n <mat-chip-grid #chipGrid class=\"ud-chip-input__grid\" aria-label=\"Chip input\">\n @for (chip of chips; track chip; let i = $index) {\n <mat-chip-row class=\"ud-chip\" (removed)=\"removeChip(i)\">\n {{ chip }}\n <button matChipRemove aria-label=\"Remove chip\">\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip-row>\n }\n <input\n class=\"ud-chip-input__field\"\n [placeholder]=\"chips.length === 0 ? placeholder : ''\"\n [matChipInputFor]=\"chipGrid\"\n [matChipInputSeparatorKeyCodes]=\"separatorKeysCodes\"\n [disabled]=\"control.disabled\"\n (matChipInputTokenEnd)=\"addChip($event)\"\n (focus)=\"focused = true\"\n (blur)=\"focused = false\" />\n </mat-chip-grid>\n </div>\n @if (hint) {\n <span class=\"ud-chip-input__hint\">{{ hint }}</span>\n }\n</div>\n", styles: [":host{display:block;width:100%}.ud-chip-input{display:flex;flex-direction:column;gap:5px;width:100%}.ud-chip-input__label{font-family:DM Sans,system-ui,sans-serif;font-size:13px;font-weight:500;color:var(--eu-text, #2a3548);line-height:1}.ud-chip-input__wrapper{display:flex;align-items:flex-start;gap:8px;background:#f8fafc;border:1px solid var(--eu-border-mid, #d8dde6);border-radius:8px;padding:6px 12px;min-height:40px;transition:border-color .18s ease,box-shadow .18s ease,background .18s ease}.ud-chip-input--focused .ud-chip-input__wrapper{border-color:var(--eu-navy, #1b2535);box-shadow:0 0 0 3px #1b253514;background:#fff}.ud-chip-input--error .ud-chip-input__wrapper{border-color:#e53935;box-shadow:0 0 0 3px #e539351a}.ud-chip-input--error.ud-chip-input--focused .ud-chip-input__wrapper{border-color:#e53935}.ud-chip-input--disabled .ud-chip-input__wrapper{background:var(--eu-bg, #f4f5f7);border-color:var(--eu-border-light, #e8eaef);cursor:not-allowed;opacity:.6}.ud-chip-input__icon{flex-shrink:0;color:var(--eu-muted, #6b7585);font-size:18px;width:18px;height:18px;margin-top:3px;line-height:1;transition:color .18s ease}.ud-chip-input--focused .ud-chip-input__icon{color:var(--eu-navy, #1b2535)}.ud-chip-input__grid{flex:1;min-width:0}:host ::ng-deep .ud-chip-input__grid .mdc-evolution-chip-set__chips{display:flex;flex-wrap:wrap;align-items:center;gap:4px;margin:0;padding:0}.ud-chip-input__field{flex:1;min-width:80px;border:none;background:transparent;outline:none;font-family:DM Sans,system-ui,sans-serif;font-size:14px;color:var(--eu-text, #2a3548);line-height:1;padding:2px 0}.ud-chip-input__field::placeholder{color:var(--eu-muted, #9099a8)}.ud-chip-input__field:disabled{cursor:not-allowed;color:var(--eu-muted, #9099a8)}:host ::ng-deep mat-chip-row.ud-chip{--mdc-chip-container-color: rgba(27, 37, 53, .07);--mdc-chip-label-text-color: var(--eu-text, #2a3548);--mdc-chip-label-text-font: \"DM Sans\", system-ui, sans-serif;--mdc-chip-label-text-size: 12px;--mdc-chip-label-text-weight: 500;--mdc-chip-container-shape-radius: 20px;--mdc-chip-with-trailing-icon-trailing-icon-color: var(--eu-muted, #6b7585);--mdc-chip-elevated-container-color: rgba(27, 37, 53, .07);height:24px;border:none}:host ::ng-deep mat-chip-row.ud-chip .mdc-evolution-chip__action--trailing{padding:0 4px}:host ::ng-deep mat-chip-row.ud-chip .mdc-evolution-chip__action--trailing .mat-icon{font-size:14px;width:14px;height:14px;line-height:14px;color:var(--eu-muted, #6b7585);transition:color .15s ease}:host ::ng-deep mat-chip-row.ud-chip .mdc-evolution-chip__action--trailing:hover .mat-icon{color:var(--eu-navy, #1b2535)}.ud-chip-input__hint{font-family:DM Sans,system-ui,sans-serif;font-size:12px;color:var(--eu-muted, #6b7585);line-height:1.3}\n"] }]
3201
+ }], propDecorators: { controlName: [{
3202
+ type: Input,
3203
+ args: [{ required: true }]
3204
+ }], label: [{
3205
+ type: Input
3206
+ }], placeholder: [{
3207
+ type: Input
3208
+ }], icon: [{
3209
+ type: Input
3210
+ }], iconFontSet: [{
3211
+ type: Input
3212
+ }], disabled: [{
3213
+ type: Input
3214
+ }], hint: [{
3215
+ type: Input
3216
+ }] } });
3217
+
3218
+ class CalendarComponent {
3219
+ slots = input([]);
3220
+ mode = input('admin');
3221
+ defaultView = input('week');
3222
+ slotDuration = input(30);
3223
+ minHour = input(8);
3224
+ maxHour = input(20);
3225
+ slotAdded = output();
3226
+ slotUpdated = output();
3227
+ slotRemoved = output();
3228
+ slotBooked = output();
3229
+ activeView = signal('week');
3230
+ navDate = signal(new Date());
3231
+ viewOptions = [
3232
+ { id: 'day', label: 'Day' },
3233
+ { id: 'week', label: 'Week' },
3234
+ { id: 'month', label: 'Month' },
3235
+ ];
3236
+ CELL_H = 56;
3237
+ TIME_COL_W = 52;
3238
+ dialog = inject(MatDialog);
3239
+ elRef = inject(ElementRef);
3240
+ // ── Drag state ─────────────────────────────────────────────────────────────
3241
+ draggingSlot = null;
3242
+ dragMoved = false;
3243
+ dragTarget = null;
3244
+ dragSlotDurationMin = 0;
3245
+ dragStartPos = null;
3246
+ constructor() {
3247
+ effect(() => {
3248
+ this.activeView.set(this.defaultView());
3249
+ }, { allowSignalWrites: true });
3250
+ }
3251
+ // ── Time axis ──────────────────────────────────────────────────────────────
3252
+ timeSlots = computed(() => {
3253
+ const slots = [];
3254
+ for (let h = this.minHour(); h < this.maxHour(); h++) {
3255
+ slots.push({ hour: h, minute: 0, label: this.formatHour(h, 0) });
3256
+ slots.push({ hour: h, minute: 30, label: '' });
3257
+ }
3258
+ return slots;
3259
+ });
3260
+ // ── Week view ──────────────────────────────────────────────────────────────
3261
+ weekDays = computed(() => {
3262
+ const d = this.navDate();
3263
+ const monday = this.getMonday(d);
3264
+ return Array.from({ length: 7 }, (_, i) => {
3265
+ const day = new Date(monday);
3266
+ day.setDate(monday.getDate() + i);
3267
+ return day;
3268
+ });
3269
+ });
3270
+ // ── Month view ─────────────────────────────────────────────────────────────
3271
+ monthWeeks = computed(() => {
3272
+ const d = this.navDate();
3273
+ const year = d.getFullYear();
3274
+ const month = d.getMonth();
3275
+ const firstDay = new Date(year, month, 1);
3276
+ const lastDay = new Date(year, month + 1, 0);
3277
+ const start = this.getMonday(firstDay);
3278
+ const weeks = [];
3279
+ let current = new Date(start);
3280
+ while (current <= lastDay || weeks.length < 6) {
3281
+ const week = [];
3282
+ for (let i = 0; i < 7; i++) {
3283
+ week.push(new Date(current));
3284
+ current.setDate(current.getDate() + 1);
3285
+ }
3286
+ weeks.push(week);
3287
+ if (current > lastDay && weeks.length >= 4)
3288
+ break;
3289
+ }
3290
+ return weeks;
3291
+ });
3292
+ // ── Header label ───────────────────────────────────────────────────────────
3293
+ headerLabel = computed(() => {
3294
+ const d = this.navDate();
3295
+ const view = this.activeView();
3296
+ if (view === 'month') {
3297
+ return d.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
3298
+ }
3299
+ if (view === 'week') {
3300
+ const days = this.weekDays();
3301
+ const start = days[0];
3302
+ const end = days[6];
3303
+ if (start.getMonth() === end.getMonth()) {
3304
+ return `${start.toLocaleDateString('en-US', { month: 'long' })} ${start.getDate()}–${end.getDate()}, ${start.getFullYear()}`;
3305
+ }
3306
+ return `${start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} – ${end.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`;
3307
+ }
3308
+ return d.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
3309
+ });
3310
+ // ── Navigation ─────────────────────────────────────────────────────────────
3311
+ navigate(dir) {
3312
+ const d = new Date(this.navDate());
3313
+ const view = this.activeView();
3314
+ if (view === 'day')
3315
+ d.setDate(d.getDate() + dir);
3316
+ else if (view === 'week')
3317
+ d.setDate(d.getDate() + dir * 7);
3318
+ else
3319
+ d.setMonth(d.getMonth() + dir);
3320
+ this.navDate.set(d);
3321
+ }
3322
+ goToday() {
3323
+ this.navDate.set(new Date());
3324
+ }
3325
+ switchView(view) {
3326
+ this.activeView.set(view);
3327
+ }
3328
+ clickDay(day) {
3329
+ this.navDate.set(new Date(day));
3330
+ this.activeView.set('day');
3331
+ }
3332
+ // ── Slot helpers ───────────────────────────────────────────────────────────
3333
+ slotsForDay(day) {
3334
+ return this.slots().filter(s => this.isSameDay(s.start, day));
3335
+ }
3336
+ slotTop(slot) {
3337
+ const startMinutes = slot.start.getHours() * 60 + slot.start.getMinutes();
3338
+ const gridStart = this.minHour() * 60;
3339
+ return ((startMinutes - gridStart) / 30) * this.CELL_H;
3340
+ }
3341
+ slotHeight(slot) {
3342
+ const durationMs = slot.end.getTime() - slot.start.getTime();
3343
+ const durationMin = durationMs / 60000;
3344
+ return Math.max((durationMin / 30) * this.CELL_H - 2, 24);
3345
+ }
3346
+ slotBg(slot) {
3347
+ if (slot.color)
3348
+ return this.hexToRgba(slot.color, 0.15);
3349
+ if (slot.booked)
3350
+ return 'rgba(229,57,53,0.10)';
3351
+ return 'rgba(27,37,53,0.10)';
3352
+ }
3353
+ slotTextColor(slot) {
3354
+ if (slot.color)
3355
+ return slot.color;
3356
+ if (slot.booked)
3357
+ return '#c62828';
3358
+ return 'var(--eu-navy)';
3359
+ }
3360
+ slotBorderColor(slot) {
3361
+ if (slot.color)
3362
+ return slot.color;
3363
+ if (slot.booked)
3364
+ return '#e53935';
3365
+ return 'var(--eu-navy)';
3366
+ }
3367
+ formatSlotTime(slot) {
3368
+ return `${this.formatTime(slot.start)} – ${this.formatTime(slot.end)}`;
3369
+ }
3370
+ isToday(day) {
3371
+ return this.isSameDay(day, new Date());
3372
+ }
3373
+ isCurrentMonth(day) {
3374
+ return day.getMonth() === this.navDate().getMonth();
3375
+ }
3376
+ dayLabel(day) {
3377
+ return day.toLocaleDateString('en-US', { weekday: 'short' });
3378
+ }
3379
+ dayNum(day) {
3380
+ return day.getDate();
3381
+ }
3382
+ // ── Cell / slot interactions ───────────────────────────────────────────────
3383
+ onCellClick(day, hour, minute) {
3384
+ if (this.mode() !== 'admin')
3385
+ return;
3386
+ this.openAddModal(day, hour, minute);
3387
+ }
3388
+ onSlotMouseDown(e, slot) {
3389
+ e.stopPropagation();
3390
+ if (this.mode() === 'student' && !slot.booked) {
3391
+ this.slotBooked.emit({ ...slot, booked: true });
3392
+ return;
3393
+ }
3394
+ if (this.mode() !== 'admin')
3395
+ return;
3396
+ this.draggingSlot = slot;
3397
+ this.dragMoved = false;
3398
+ this.dragStartPos = { x: e.clientX, y: e.clientY };
3399
+ this.dragSlotDurationMin = (slot.end.getTime() - slot.start.getTime()) / 60000;
3400
+ }
3401
+ onDocMouseMove(e) {
3402
+ if (!this.draggingSlot || !this.dragStartPos)
3403
+ return;
3404
+ const dx = e.clientX - this.dragStartPos.x;
3405
+ const dy = e.clientY - this.dragStartPos.y;
3406
+ if (!this.dragMoved && Math.sqrt(dx * dx + dy * dy) > 6) {
3407
+ this.dragMoved = true;
3408
+ document.body.style.cursor = 'grabbing';
3409
+ document.body.style.userSelect = 'none';
3410
+ }
3411
+ if (this.dragMoved) {
3412
+ this.dragTarget = this.getGridTarget(e);
3413
+ }
3414
+ }
3415
+ onDocMouseUp(e) {
3416
+ if (!this.draggingSlot)
3417
+ return;
3418
+ if (this.dragMoved) {
3419
+ const target = this.getGridTarget(e);
3420
+ if (target) {
3421
+ const newStart = new Date(target.day);
3422
+ newStart.setHours(target.hour, target.minute, 0, 0);
3423
+ const newEnd = new Date(newStart.getTime() + this.dragSlotDurationMin * 60000);
3424
+ this.slotUpdated.emit({ ...this.draggingSlot, start: newStart, end: newEnd });
3425
+ }
3426
+ }
3427
+ else {
3428
+ this.openEditModal(this.draggingSlot);
3429
+ }
3430
+ this.draggingSlot = null;
3431
+ this.dragMoved = false;
3432
+ this.dragTarget = null;
3433
+ this.dragStartPos = null;
3434
+ document.body.style.cursor = '';
3435
+ document.body.style.userSelect = '';
3436
+ }
3437
+ previewTop(target) {
3438
+ const min = target.hour * 60 + target.minute;
3439
+ return ((min - this.minHour() * 60) / 30) * this.CELL_H;
3440
+ }
3441
+ previewHeight() {
3442
+ return Math.max((this.dragSlotDurationMin / 30) * this.CELL_H - 2, 24);
3443
+ }
3444
+ getGridTarget(e) {
3445
+ if (this.activeView() === 'month')
3446
+ return null;
3447
+ const scrollEl = this.elRef.nativeElement.querySelector('.ud-cal__week-scroll');
3448
+ const headerEl = this.elRef.nativeElement.querySelector('.ud-cal__week-header');
3449
+ const gridEl = this.elRef.nativeElement.querySelector('.ud-cal__week-grid');
3450
+ if (!scrollEl || !headerEl || !gridEl)
3451
+ return null;
3452
+ const scrollRect = scrollEl.getBoundingClientRect();
3453
+ const headerH = headerEl.getBoundingClientRect().height;
3454
+ const mouseYInGrid = (e.clientY - scrollRect.top) + scrollEl.scrollTop - headerH;
3455
+ if (mouseYInGrid < 0)
3456
+ return null;
3457
+ const slotIndex = Math.floor(mouseYInGrid / this.CELL_H);
3458
+ const ts = this.timeSlots();
3459
+ if (slotIndex >= ts.length)
3460
+ return null;
3461
+ const { hour, minute } = ts[slotIndex];
3462
+ const gridRect = gridEl.getBoundingClientRect();
3463
+ const days = this.activeView() === 'day' ? [this.navDate()] : this.weekDays();
3464
+ const totalDayW = gridRect.width - this.TIME_COL_W;
3465
+ const relX = e.clientX - gridRect.left - this.TIME_COL_W;
3466
+ if (relX < 0 || relX >= totalDayW)
3467
+ return null;
3468
+ const dayIndex = Math.min(days.length - 1, Math.floor(relX / (totalDayW / days.length)));
3469
+ return { day: new Date(days[dayIndex]), hour, minute };
3470
+ }
3471
+ // ── Modal open ─────────────────────────────────────────────────────────────
3472
+ openAddModal(day, hour, minute) {
3473
+ const base = day ? new Date(day) : new Date();
3474
+ const h = hour ?? base.getHours();
3475
+ const m = minute ?? 0;
3476
+ const startDate = new Date(base);
3477
+ startDate.setHours(h, m, 0, 0);
3478
+ const totalEnd = m + this.slotDuration();
3479
+ const endDate = new Date(base);
3480
+ endDate.setHours(h + Math.floor(totalEnd / 60), totalEnd % 60, 0, 0);
3481
+ const form = this.buildForm({
3482
+ date: new Date(base),
3483
+ startTime: this.formatTime(startDate),
3484
+ endTime: this.formatTime(endDate),
3485
+ });
3486
+ const ref = this.dialog.open(ModalComponent, {
3487
+ width: '480px',
3488
+ data: {
3489
+ eyebrow: 'Calendar',
3490
+ title: 'Add time slot',
3491
+ formGroup: form,
3492
+ forms: this.slotForms(),
3493
+ save: (v) => this.slotAdded.emit(this.buildSlot(crypto.randomUUID(), v)),
3494
+ },
3495
+ });
3496
+ form.get('startTime')?.valueChanges
3497
+ .pipe(takeUntil(ref.afterClosed()))
3498
+ .subscribe(timeStr => {
3499
+ if (!timeStr)
3500
+ return;
3501
+ form.get('endTime')?.setValue(this.shiftTimeStr(timeStr, this.slotDuration()));
3502
+ });
3503
+ }
3504
+ openEditModal(slot) {
3505
+ const form = this.buildForm({
3506
+ title: slot.title ?? '',
3507
+ date: new Date(slot.start),
3508
+ startTime: this.formatTime(slot.start),
3509
+ endTime: this.formatTime(slot.end),
3510
+ bookedBy: slot.bookedBy ?? '',
3511
+ booked: slot.booked ?? false,
3512
+ });
3513
+ const ref = this.dialog.open(ModalComponent, {
3514
+ width: '480px',
3515
+ data: {
3516
+ eyebrow: 'Calendar',
3517
+ title: 'Edit time slot',
3518
+ formGroup: form,
3519
+ forms: this.slotForms(),
3520
+ save: (v) => {
3521
+ const updated = this.buildSlot(slot.id, v);
3522
+ if (updated.booked && !slot.booked) {
3523
+ this.slotBooked.emit(updated);
3524
+ }
3525
+ else {
3526
+ this.slotUpdated.emit(updated);
3527
+ }
3528
+ },
3529
+ deleteLabel: 'Delete slot',
3530
+ delete: () => this.slotRemoved.emit(slot.id),
3531
+ },
3532
+ });
3533
+ form.get('startTime')?.valueChanges
3534
+ .pipe(takeUntil(ref.afterClosed()))
3535
+ .subscribe(timeStr => {
3536
+ if (!timeStr)
3537
+ return;
3538
+ form.get('endTime')?.setValue(this.shiftTimeStr(timeStr, this.slotDuration()));
3539
+ });
3540
+ }
3541
+ // ── Helpers ────────────────────────────────────────────────────────────────
3542
+ buildForm(defaults) {
3543
+ return new FormGroup({
3544
+ title: new FormControl(defaults.title ?? ''),
3545
+ date: new FormControl(defaults.date ?? null, Validators.required),
3546
+ startTime: new FormControl(defaults.startTime ?? null, Validators.required),
3547
+ endTime: new FormControl(defaults.endTime ?? null, Validators.required),
3548
+ bookedBy: new FormControl(defaults.bookedBy ?? ''),
3549
+ booked: new FormControl(defaults.booked ?? false),
3550
+ });
3551
+ }
3552
+ slotForms() {
3553
+ return [
3554
+ { type: ModalInputType.INPUT, title: 'Title', property: 'title', placeholder: 'e.g. Office hours' },
3555
+ { type: ModalInputType.DATETIME, title: 'Date', property: 'date' },
3556
+ { type: ModalInputType.TIME, title: 'Start Time', property: 'startTime', intervalMinutes: this.slotDuration() },
3557
+ { type: ModalInputType.TIME, title: 'End Time', property: 'endTime', intervalMinutes: this.slotDuration() },
3558
+ { type: ModalInputType.INPUT, title: 'Booked By', property: 'bookedBy', placeholder: 'Student name or ID' },
3559
+ {
3560
+ type: ModalInputType.OPTIONS,
3561
+ title: 'Status',
3562
+ property: 'booked',
3563
+ availableOptions: [
3564
+ { value: false, label: 'Available' },
3565
+ { value: true, label: 'Booked' },
3566
+ ],
3567
+ },
3568
+ ];
3569
+ }
3570
+ buildSlot(id, v) {
3571
+ const start = this.applyTimeStr(new Date(v.date), v.startTime);
3572
+ const end = this.applyTimeStr(new Date(v.date), v.endTime);
3573
+ return {
3574
+ id,
3575
+ title: v.title || undefined,
3576
+ start,
3577
+ end,
3578
+ booked: !!v.booked,
3579
+ bookedBy: v.bookedBy || undefined,
3580
+ };
3581
+ }
3582
+ shiftTimeStr(timeStr, minutes) {
3583
+ const m = timeStr?.match(/(\d+):(\d+)\s*(AM|PM)/i);
3584
+ if (!m)
3585
+ return timeStr;
3586
+ let h = parseInt(m[1], 10);
3587
+ const min = parseInt(m[2], 10);
3588
+ if (m[3].toUpperCase() === 'PM' && h !== 12)
3589
+ h += 12;
3590
+ if (m[3].toUpperCase() === 'AM' && h === 12)
3591
+ h = 0;
3592
+ const total = h * 60 + min + minutes;
3593
+ const d = new Date();
3594
+ d.setHours(Math.floor(total / 60) % 24, total % 60, 0, 0);
3595
+ return this.formatTime(d);
3596
+ }
3597
+ applyTimeStr(base, timeStr) {
3598
+ const m = timeStr?.match(/(\d+):(\d+)\s*(AM|PM)/i);
3599
+ if (m) {
3600
+ let h = parseInt(m[1], 10);
3601
+ const min = parseInt(m[2], 10);
3602
+ if (m[3].toUpperCase() === 'PM' && h !== 12)
3603
+ h += 12;
3604
+ if (m[3].toUpperCase() === 'AM' && h === 12)
3605
+ h = 0;
3606
+ base.setHours(h, min, 0, 0);
3607
+ }
3608
+ return base;
3609
+ }
3610
+ getMonday(d) {
3611
+ const day = new Date(d);
3612
+ const dow = day.getDay();
3613
+ const diff = dow === 0 ? -6 : 1 - dow;
3614
+ day.setDate(day.getDate() + diff);
3615
+ day.setHours(0, 0, 0, 0);
3616
+ return day;
3617
+ }
3618
+ isSameDay(a, b) {
3619
+ return a.getFullYear() === b.getFullYear() &&
3620
+ a.getMonth() === b.getMonth() &&
3621
+ a.getDate() === b.getDate();
3622
+ }
3623
+ formatHour(h, m) {
3624
+ const period = h < 12 ? 'am' : 'pm';
3625
+ const hour = h % 12 || 12;
3626
+ return m === 0 ? `${hour}${period}` : `${hour}:${m.toString().padStart(2, '0')}`;
3627
+ }
3628
+ formatTime(d) {
3629
+ return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
3630
+ }
3631
+ hexToRgba(hex, alpha) {
3632
+ const r = parseInt(hex.slice(1, 3), 16);
3633
+ const g = parseInt(hex.slice(3, 5), 16);
3634
+ const b = parseInt(hex.slice(5, 7), 16);
3635
+ return `rgba(${r},${g},${b},${alpha})`;
3636
+ }
3637
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: CalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3638
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.19", type: CalendarComponent, isStandalone: true, selector: "ud-calendar", inputs: { slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, defaultView: { classPropertyName: "defaultView", publicName: "defaultView", isSignal: true, isRequired: false, transformFunction: null }, slotDuration: { classPropertyName: "slotDuration", publicName: "slotDuration", isSignal: true, isRequired: false, transformFunction: null }, minHour: { classPropertyName: "minHour", publicName: "minHour", isSignal: true, isRequired: false, transformFunction: null }, maxHour: { classPropertyName: "maxHour", publicName: "maxHour", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { slotAdded: "slotAdded", slotUpdated: "slotUpdated", slotRemoved: "slotRemoved", slotBooked: "slotBooked" }, host: { listeners: { "document:mousemove": "onDocMouseMove($event)", "document:mouseup": "onDocMouseUp($event)" } }, ngImport: i0, template: "<div class=\"ud-cal\" #calendarHost>\n\n <!-- Header -->\n <div class=\"ud-cal__header\">\n <div class=\"ud-cal__nav\">\n <ud-button variant=\"icon-only\" color=\"secondary\" size=\"sm\" icon=\"chevron_left\" (click)=\"navigate(-1)\" />\n <ud-button variant=\"icon-only\" color=\"secondary\" size=\"sm\" icon=\"chevron_right\" (click)=\"navigate(1)\" />\n <ud-button variant=\"stroked\" color=\"secondary\" size=\"sm\" (click)=\"goToday()\">Today</ud-button>\n </div>\n\n <span class=\"ud-cal__period\">{{ headerLabel() }}</span>\n\n <div class=\"ud-cal__header-right\">\n <div class=\"ud-cal__view-switcher\">\n @for (v of viewOptions; track v.id) {\n <button\n class=\"ud-cal__view-btn\"\n [class.ud-cal__view-btn--active]=\"activeView() === v.id\"\n (click)=\"switchView(v.id)\"\n type=\"button\">\n {{ v.label }}\n </button>\n }\n </div>\n @if (mode() === 'admin') {\n <ud-button variant=\"flat\" color=\"primary\" size=\"sm\" icon=\"add\" (click)=\"openAddModal()\">\n Add slot\n </ud-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 WEEK VIEW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeView() === 'week') {\n <div class=\"ud-cal__week-scroll\">\n <div class=\"ud-cal__week-header\">\n <div class=\"ud-cal__time-gutter\"></div>\n @for (day of weekDays(); track day.toISOString()) {\n <div class=\"ud-cal__day-header\" [class.ud-cal__day-header--today]=\"isToday(day)\">\n <span class=\"ud-cal__day-name\">{{ dayLabel(day) }}</span>\n <span class=\"ud-cal__day-num\" [class.ud-cal__day-num--today]=\"isToday(day)\">{{ dayNum(day) }}</span>\n </div>\n }\n </div>\n <div class=\"ud-cal__week-grid\">\n <div class=\"ud-cal__time-col\">\n @for (ts of timeSlots(); track ts.hour + ':' + ts.minute) {\n <div class=\"ud-cal__time-cell\">\n @if (ts.label) {\n <span class=\"ud-cal__time-label\">{{ ts.label }}</span>\n }\n </div>\n }\n </div>\n @for (day of weekDays(); track day.toISOString()) {\n <div class=\"ud-cal__day-col\" [class.ud-cal__day-col--today]=\"isToday(day)\">\n @for (ts of timeSlots(); track ts.hour + ':' + ts.minute) {\n <div\n class=\"ud-cal__grid-cell\"\n [class.ud-cal__grid-cell--half]=\"ts.minute === 30\"\n (click)=\"onCellClick(day, ts.hour, ts.minute)\">\n </div>\n }\n @for (slot of slotsForDay(day); track slot.id) {\n <div\n class=\"ud-cal__slot\"\n [class.ud-cal__slot--booked]=\"slot.booked\"\n [class.ud-cal__slot--clickable]=\"mode() === 'admin' || (mode() === 'student' && !slot.booked)\"\n [class.ud-cal__slot--dragging]=\"draggingSlot?.id === slot.id && dragMoved\"\n [style.top.px]=\"slotTop(slot)\"\n [style.height.px]=\"slotHeight(slot)\"\n [style.background]=\"slotBg(slot)\"\n [style.color]=\"slotTextColor(slot)\"\n [style.border-color]=\"slotBorderColor(slot)\"\n (mousedown)=\"onSlotMouseDown($event, slot)\">\n <div class=\"ud-cal__slot-inner\">\n @if (slot.booked) {\n <mat-icon class=\"ud-cal__slot-lock\">lock</mat-icon>\n }\n @if (slot.title) {\n <span class=\"ud-cal__slot-title\">{{ slot.title }}</span>\n }\n </div>\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n @if (slot.booked && slot.bookedBy) {\n <span class=\"ud-cal__slot-booked-by\">{{ slot.bookedBy }}</span>\n }\n </div>\n }\n @if (draggingSlot && dragMoved && dragTarget && isSameDay(dragTarget.day, day)) {\n <div\n class=\"ud-cal__slot ud-cal__slot--drag-preview\"\n [style.top.px]=\"previewTop(dragTarget)\"\n [style.height.px]=\"previewHeight()\"\n [style.background]=\"slotBg(draggingSlot)\"\n [style.color]=\"slotTextColor(draggingSlot)\"\n [style.border-color]=\"slotBorderColor(draggingSlot)\">\n @if (draggingSlot.title) {\n <span class=\"ud-cal__slot-title\">{{ draggingSlot.title }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 MONTH VIEW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeView() === 'month') {\n <div class=\"ud-cal__month\">\n <div class=\"ud-cal__month-header\">\n @for (name of ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; track name) {\n <div class=\"ud-cal__month-day-name\">{{ name }}</div>\n }\n </div>\n <div class=\"ud-cal__month-grid\">\n @for (week of monthWeeks(); track $index) {\n @for (day of week; track day.toISOString()) {\n <div\n class=\"ud-cal__month-cell\"\n [class.ud-cal__month-cell--today]=\"isToday(day)\"\n [class.ud-cal__month-cell--other]=\"!isCurrentMonth(day)\"\n (click)=\"clickDay(day)\">\n <span class=\"ud-cal__month-num\" [class.ud-cal__month-num--today]=\"isToday(day)\">\n {{ dayNum(day) }}\n </span>\n <div class=\"ud-cal__month-dots\">\n @for (slot of slotsForDay(day).slice(0, 3); track slot.id) {\n <span\n class=\"ud-cal__month-dot\"\n [style.background]=\"slotBorderColor(slot)\"\n [title]=\"slot.title ?? formatSlotTime(slot)\">\n </span>\n }\n @if (slotsForDay(day).length > 3) {\n <span class=\"ud-cal__month-more\">+{{ slotsForDay(day).length - 3 }}</span>\n }\n </div>\n </div>\n }\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 DAY VIEW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeView() === 'day') {\n <div class=\"ud-cal__week-scroll\">\n <div class=\"ud-cal__week-header\">\n <div class=\"ud-cal__time-gutter\"></div>\n <div class=\"ud-cal__day-header\" [class.ud-cal__day-header--today]=\"isToday(navDate())\">\n <span class=\"ud-cal__day-name\">{{ dayLabel(navDate()) }}</span>\n <span class=\"ud-cal__day-num\" [class.ud-cal__day-num--today]=\"isToday(navDate())\">{{ dayNum(navDate()) }}</span>\n </div>\n </div>\n <div class=\"ud-cal__week-grid\">\n <div class=\"ud-cal__time-col\">\n @for (ts of timeSlots(); track ts.hour + ':' + ts.minute) {\n <div class=\"ud-cal__time-cell\">\n @if (ts.label) {\n <span class=\"ud-cal__time-label\">{{ ts.label }}</span>\n }\n </div>\n }\n </div>\n <div class=\"ud-cal__day-col\" [class.ud-cal__day-col--today]=\"isToday(navDate())\">\n @for (ts of timeSlots(); track ts.hour + ':' + ts.minute) {\n <div\n class=\"ud-cal__grid-cell\"\n [class.ud-cal__grid-cell--half]=\"ts.minute === 30\"\n (click)=\"onCellClick(navDate(), ts.hour, ts.minute)\">\n </div>\n }\n @for (slot of slotsForDay(navDate()); track slot.id) {\n <div\n class=\"ud-cal__slot\"\n [class.ud-cal__slot--booked]=\"slot.booked\"\n [class.ud-cal__slot--clickable]=\"mode() === 'admin' || (mode() === 'student' && !slot.booked)\"\n [class.ud-cal__slot--dragging]=\"draggingSlot?.id === slot.id && dragMoved\"\n [style.top.px]=\"slotTop(slot)\"\n [style.height.px]=\"slotHeight(slot)\"\n [style.background]=\"slotBg(slot)\"\n [style.color]=\"slotTextColor(slot)\"\n [style.border-color]=\"slotBorderColor(slot)\"\n (mousedown)=\"onSlotMouseDown($event, slot)\">\n <div class=\"ud-cal__slot-inner\">\n @if (slot.booked) {\n <mat-icon class=\"ud-cal__slot-lock\">lock</mat-icon>\n }\n @if (slot.title) {\n <span class=\"ud-cal__slot-title\">{{ slot.title }}</span>\n }\n </div>\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n @if (slot.booked && slot.bookedBy) {\n <span class=\"ud-cal__slot-booked-by\">{{ slot.bookedBy }}</span>\n }\n </div>\n }\n @if (draggingSlot && dragMoved && dragTarget) {\n <div\n class=\"ud-cal__slot ud-cal__slot--drag-preview\"\n [style.top.px]=\"previewTop(dragTarget)\"\n [style.height.px]=\"previewHeight()\"\n [style.background]=\"slotBg(draggingSlot)\"\n [style.color]=\"slotTextColor(draggingSlot)\"\n [style.border-color]=\"slotBorderColor(draggingSlot)\">\n @if (draggingSlot.title) {\n <span class=\"ud-cal__slot-title\">{{ draggingSlot.title }}</span>\n }\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n</div>\n\n", styles: [":host{display:block;width:100%;font-family:DM Sans,system-ui,sans-serif}.ud-cal{position:relative;background:#fff;border:1px solid var(--eu-border-mid, #d8dde6);border-radius:12px;box-shadow:0 2px 8px #1b25350f;overflow:hidden}.ud-cal__header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--eu-border-light, #e8eaef);gap:12px;flex-wrap:wrap}.ud-cal__nav{display:flex;align-items:center;gap:4px}.ud-cal__period{flex:1;text-align:center;font-size:14px;font-weight:600;color:var(--eu-text, #2a3548);white-space:nowrap}.ud-cal__header-right{display:flex;align-items:center;gap:10px}.ud-cal__view-switcher{display:flex;align-items:center;background:var(--eu-bg, #f4f5f7);border-radius:8px;padding:3px;gap:2px}.ud-cal__view-btn{padding:4px 12px;border:none;border-radius:6px;background:transparent;font-family:DM Sans,system-ui,sans-serif;font-size:12px;font-weight:500;color:var(--eu-muted, #6b7585);cursor:pointer;transition:background .15s,color .15s}.ud-cal__view-btn--active{background:var(--eu-navy, #1b2535);color:#fff}.ud-cal__view-btn:not(.ud-cal__view-btn--active):hover{background:#1b253514;color:var(--eu-text, #2a3548)}.ud-cal__week-scroll{overflow-y:auto;max-height:580px}.ud-cal__week-header{position:sticky;top:0;z-index:10;display:flex;background:#fafbfc;border-bottom:1px solid var(--eu-border-light, #e8eaef);flex-shrink:0}.ud-cal__time-gutter{width:52px;flex-shrink:0;border-right:1px solid var(--eu-border-light, #e8eaef)}.ud-cal__day-header{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:8px 4px;gap:2px;border-right:1px solid var(--eu-border-light, #e8eaef)}.ud-cal__day-header:last-child{border-right:none}.ud-cal__day-header--today{background:#1b25350a}.ud-cal__day-name{font-size:10px;font-weight:600;color:var(--eu-muted, #6b7585);text-transform:uppercase;letter-spacing:.06em}.ud-cal__day-num{font-size:15px;font-weight:600;color:var(--eu-text, #2a3548)}.ud-cal__day-num--today{display:flex;align-items:center;justify-content:center;width:26px;height:26px;background:var(--eu-navy, #1b2535);color:#fff;border-radius:50%;font-size:13px}.ud-cal__week-grid{display:flex}.ud-cal__time-col{width:52px;flex-shrink:0;border-right:1px solid var(--eu-border-light, #e8eaef)}.ud-cal__time-cell{height:56px;position:relative;display:flex;align-items:flex-start;justify-content:flex-end;padding:0 6px}.ud-cal__time-label{font-size:10px;color:var(--eu-muted, #9099a8);font-weight:500;white-space:nowrap;margin-top:-6px}.ud-cal__day-col{flex:1;position:relative;border-right:1px solid var(--eu-border-light, #e8eaef)}.ud-cal__day-col:last-child{border-right:none}.ud-cal__day-col--today{background:#1b253506}.ud-cal__grid-cell{height:56px;border-bottom:1px solid var(--eu-border-light, #e8eaef);transition:background .1s;cursor:pointer}.ud-cal__grid-cell--half{border-bottom-style:dashed;border-bottom-color:#d8dde673}.ud-cal__grid-cell:hover{background:#1b253508}.ud-cal__slot{position:absolute;left:3px;right:3px;border-left:3px solid;border-radius:6px;padding:3px 6px;display:flex;flex-direction:column;gap:1px;overflow:hidden;z-index:1;transition:filter .15s,transform .1s}.ud-cal__slot--clickable{cursor:grab}.ud-cal__slot--clickable:hover{filter:brightness(.93);transform:translate(1px)}.ud-cal__slot-inner{display:flex;align-items:center;gap:3px;overflow:hidden;min-width:0}.ud-cal__slot-title{font-size:11px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.2;min-width:0}.ud-cal__slot-time{font-size:10px;opacity:.8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.2}.ud-cal__slot--dragging{opacity:.3;pointer-events:none}.ud-cal__slot--drag-preview{pointer-events:none;opacity:.75;border-left-style:dashed;border-top:1px dashed currentColor;z-index:2}.ud-cal__slot--booked{background-image:repeating-linear-gradient(-45deg,transparent,transparent 4px,rgba(229,57,53,.07) 4px,rgba(229,57,53,.07) 8px)!important;cursor:not-allowed;border-top:1px solid rgba(229,57,53,.18)}.ud-cal__slot--booked:hover{filter:none!important;transform:none!important}.ud-cal__slot--booked .ud-cal__slot-title{text-decoration:line-through;text-decoration-color:#c6282866;text-decoration-thickness:1px;opacity:.9}.ud-cal__slot-lock{font-size:10px!important;width:10px!important;height:10px!important;line-height:10px!important;flex-shrink:0;opacity:.7}.ud-cal__slot-booked-by{font-size:9px;font-weight:500;opacity:.6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:.01em;margin-top:1px}.ud-cal__month{display:flex;flex-direction:column}.ud-cal__month-header{display:grid;grid-template-columns:repeat(7,1fr);border-bottom:1px solid var(--eu-border-light, #e8eaef);background:#fafbfc}.ud-cal__month-day-name{text-align:center;padding:8px 4px;font-size:10px;font-weight:600;color:var(--eu-muted, #6b7585);text-transform:uppercase;letter-spacing:.06em;border-right:1px solid var(--eu-border-light, #e8eaef)}.ud-cal__month-day-name:last-child{border-right:none}.ud-cal__month-grid{display:grid;grid-template-columns:repeat(7,1fr)}.ud-cal__month-cell{min-height:80px;padding:6px;border-right:1px solid var(--eu-border-light, #e8eaef);border-bottom:1px solid var(--eu-border-light, #e8eaef);cursor:pointer;transition:background .12s}.ud-cal__month-cell:nth-child(7n){border-right:none}.ud-cal__month-cell:hover{background:#1b253508}.ud-cal__month-cell--today{background:#1b25350a}.ud-cal__month-cell--other{opacity:.4}.ud-cal__month-num{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;font-size:12px;font-weight:600;color:var(--eu-text, #2a3548);border-radius:50%}.ud-cal__month-num--today{background:var(--eu-navy, #1b2535);color:#fff}.ud-cal__month-dots{display:flex;flex-wrap:wrap;align-items:center;gap:3px;margin-top:4px}.ud-cal__month-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.ud-cal__month-more{font-size:9px;font-weight:600;color:var(--eu-muted, #6b7585)}\n"], dependencies: [{ kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: UdButtonComponent, selector: "ud-button", inputs: ["variant", "color", "size", "type", "icon", "iconPosition", "iconFontSet", "loading", "disabled", "fullWidth"] }] });
3639
+ }
3640
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: CalendarComponent, decorators: [{
3641
+ type: Component,
3642
+ args: [{ selector: 'ud-calendar', standalone: true, imports: [MatIcon, UdButtonComponent], template: "<div class=\"ud-cal\" #calendarHost>\n\n <!-- Header -->\n <div class=\"ud-cal__header\">\n <div class=\"ud-cal__nav\">\n <ud-button variant=\"icon-only\" color=\"secondary\" size=\"sm\" icon=\"chevron_left\" (click)=\"navigate(-1)\" />\n <ud-button variant=\"icon-only\" color=\"secondary\" size=\"sm\" icon=\"chevron_right\" (click)=\"navigate(1)\" />\n <ud-button variant=\"stroked\" color=\"secondary\" size=\"sm\" (click)=\"goToday()\">Today</ud-button>\n </div>\n\n <span class=\"ud-cal__period\">{{ headerLabel() }}</span>\n\n <div class=\"ud-cal__header-right\">\n <div class=\"ud-cal__view-switcher\">\n @for (v of viewOptions; track v.id) {\n <button\n class=\"ud-cal__view-btn\"\n [class.ud-cal__view-btn--active]=\"activeView() === v.id\"\n (click)=\"switchView(v.id)\"\n type=\"button\">\n {{ v.label }}\n </button>\n }\n </div>\n @if (mode() === 'admin') {\n <ud-button variant=\"flat\" color=\"primary\" size=\"sm\" icon=\"add\" (click)=\"openAddModal()\">\n Add slot\n </ud-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 WEEK VIEW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeView() === 'week') {\n <div class=\"ud-cal__week-scroll\">\n <div class=\"ud-cal__week-header\">\n <div class=\"ud-cal__time-gutter\"></div>\n @for (day of weekDays(); track day.toISOString()) {\n <div class=\"ud-cal__day-header\" [class.ud-cal__day-header--today]=\"isToday(day)\">\n <span class=\"ud-cal__day-name\">{{ dayLabel(day) }}</span>\n <span class=\"ud-cal__day-num\" [class.ud-cal__day-num--today]=\"isToday(day)\">{{ dayNum(day) }}</span>\n </div>\n }\n </div>\n <div class=\"ud-cal__week-grid\">\n <div class=\"ud-cal__time-col\">\n @for (ts of timeSlots(); track ts.hour + ':' + ts.minute) {\n <div class=\"ud-cal__time-cell\">\n @if (ts.label) {\n <span class=\"ud-cal__time-label\">{{ ts.label }}</span>\n }\n </div>\n }\n </div>\n @for (day of weekDays(); track day.toISOString()) {\n <div class=\"ud-cal__day-col\" [class.ud-cal__day-col--today]=\"isToday(day)\">\n @for (ts of timeSlots(); track ts.hour + ':' + ts.minute) {\n <div\n class=\"ud-cal__grid-cell\"\n [class.ud-cal__grid-cell--half]=\"ts.minute === 30\"\n (click)=\"onCellClick(day, ts.hour, ts.minute)\">\n </div>\n }\n @for (slot of slotsForDay(day); track slot.id) {\n <div\n class=\"ud-cal__slot\"\n [class.ud-cal__slot--booked]=\"slot.booked\"\n [class.ud-cal__slot--clickable]=\"mode() === 'admin' || (mode() === 'student' && !slot.booked)\"\n [class.ud-cal__slot--dragging]=\"draggingSlot?.id === slot.id && dragMoved\"\n [style.top.px]=\"slotTop(slot)\"\n [style.height.px]=\"slotHeight(slot)\"\n [style.background]=\"slotBg(slot)\"\n [style.color]=\"slotTextColor(slot)\"\n [style.border-color]=\"slotBorderColor(slot)\"\n (mousedown)=\"onSlotMouseDown($event, slot)\">\n <div class=\"ud-cal__slot-inner\">\n @if (slot.booked) {\n <mat-icon class=\"ud-cal__slot-lock\">lock</mat-icon>\n }\n @if (slot.title) {\n <span class=\"ud-cal__slot-title\">{{ slot.title }}</span>\n }\n </div>\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n @if (slot.booked && slot.bookedBy) {\n <span class=\"ud-cal__slot-booked-by\">{{ slot.bookedBy }}</span>\n }\n </div>\n }\n @if (draggingSlot && dragMoved && dragTarget && isSameDay(dragTarget.day, day)) {\n <div\n class=\"ud-cal__slot ud-cal__slot--drag-preview\"\n [style.top.px]=\"previewTop(dragTarget)\"\n [style.height.px]=\"previewHeight()\"\n [style.background]=\"slotBg(draggingSlot)\"\n [style.color]=\"slotTextColor(draggingSlot)\"\n [style.border-color]=\"slotBorderColor(draggingSlot)\">\n @if (draggingSlot.title) {\n <span class=\"ud-cal__slot-title\">{{ draggingSlot.title }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 MONTH VIEW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeView() === 'month') {\n <div class=\"ud-cal__month\">\n <div class=\"ud-cal__month-header\">\n @for (name of ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; track name) {\n <div class=\"ud-cal__month-day-name\">{{ name }}</div>\n }\n </div>\n <div class=\"ud-cal__month-grid\">\n @for (week of monthWeeks(); track $index) {\n @for (day of week; track day.toISOString()) {\n <div\n class=\"ud-cal__month-cell\"\n [class.ud-cal__month-cell--today]=\"isToday(day)\"\n [class.ud-cal__month-cell--other]=\"!isCurrentMonth(day)\"\n (click)=\"clickDay(day)\">\n <span class=\"ud-cal__month-num\" [class.ud-cal__month-num--today]=\"isToday(day)\">\n {{ dayNum(day) }}\n </span>\n <div class=\"ud-cal__month-dots\">\n @for (slot of slotsForDay(day).slice(0, 3); track slot.id) {\n <span\n class=\"ud-cal__month-dot\"\n [style.background]=\"slotBorderColor(slot)\"\n [title]=\"slot.title ?? formatSlotTime(slot)\">\n </span>\n }\n @if (slotsForDay(day).length > 3) {\n <span class=\"ud-cal__month-more\">+{{ slotsForDay(day).length - 3 }}</span>\n }\n </div>\n </div>\n }\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 DAY VIEW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeView() === 'day') {\n <div class=\"ud-cal__week-scroll\">\n <div class=\"ud-cal__week-header\">\n <div class=\"ud-cal__time-gutter\"></div>\n <div class=\"ud-cal__day-header\" [class.ud-cal__day-header--today]=\"isToday(navDate())\">\n <span class=\"ud-cal__day-name\">{{ dayLabel(navDate()) }}</span>\n <span class=\"ud-cal__day-num\" [class.ud-cal__day-num--today]=\"isToday(navDate())\">{{ dayNum(navDate()) }}</span>\n </div>\n </div>\n <div class=\"ud-cal__week-grid\">\n <div class=\"ud-cal__time-col\">\n @for (ts of timeSlots(); track ts.hour + ':' + ts.minute) {\n <div class=\"ud-cal__time-cell\">\n @if (ts.label) {\n <span class=\"ud-cal__time-label\">{{ ts.label }}</span>\n }\n </div>\n }\n </div>\n <div class=\"ud-cal__day-col\" [class.ud-cal__day-col--today]=\"isToday(navDate())\">\n @for (ts of timeSlots(); track ts.hour + ':' + ts.minute) {\n <div\n class=\"ud-cal__grid-cell\"\n [class.ud-cal__grid-cell--half]=\"ts.minute === 30\"\n (click)=\"onCellClick(navDate(), ts.hour, ts.minute)\">\n </div>\n }\n @for (slot of slotsForDay(navDate()); track slot.id) {\n <div\n class=\"ud-cal__slot\"\n [class.ud-cal__slot--booked]=\"slot.booked\"\n [class.ud-cal__slot--clickable]=\"mode() === 'admin' || (mode() === 'student' && !slot.booked)\"\n [class.ud-cal__slot--dragging]=\"draggingSlot?.id === slot.id && dragMoved\"\n [style.top.px]=\"slotTop(slot)\"\n [style.height.px]=\"slotHeight(slot)\"\n [style.background]=\"slotBg(slot)\"\n [style.color]=\"slotTextColor(slot)\"\n [style.border-color]=\"slotBorderColor(slot)\"\n (mousedown)=\"onSlotMouseDown($event, slot)\">\n <div class=\"ud-cal__slot-inner\">\n @if (slot.booked) {\n <mat-icon class=\"ud-cal__slot-lock\">lock</mat-icon>\n }\n @if (slot.title) {\n <span class=\"ud-cal__slot-title\">{{ slot.title }}</span>\n }\n </div>\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n @if (slot.booked && slot.bookedBy) {\n <span class=\"ud-cal__slot-booked-by\">{{ slot.bookedBy }}</span>\n }\n </div>\n }\n @if (draggingSlot && dragMoved && dragTarget) {\n <div\n class=\"ud-cal__slot ud-cal__slot--drag-preview\"\n [style.top.px]=\"previewTop(dragTarget)\"\n [style.height.px]=\"previewHeight()\"\n [style.background]=\"slotBg(draggingSlot)\"\n [style.color]=\"slotTextColor(draggingSlot)\"\n [style.border-color]=\"slotBorderColor(draggingSlot)\">\n @if (draggingSlot.title) {\n <span class=\"ud-cal__slot-title\">{{ draggingSlot.title }}</span>\n }\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n</div>\n\n", styles: [":host{display:block;width:100%;font-family:DM Sans,system-ui,sans-serif}.ud-cal{position:relative;background:#fff;border:1px solid var(--eu-border-mid, #d8dde6);border-radius:12px;box-shadow:0 2px 8px #1b25350f;overflow:hidden}.ud-cal__header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--eu-border-light, #e8eaef);gap:12px;flex-wrap:wrap}.ud-cal__nav{display:flex;align-items:center;gap:4px}.ud-cal__period{flex:1;text-align:center;font-size:14px;font-weight:600;color:var(--eu-text, #2a3548);white-space:nowrap}.ud-cal__header-right{display:flex;align-items:center;gap:10px}.ud-cal__view-switcher{display:flex;align-items:center;background:var(--eu-bg, #f4f5f7);border-radius:8px;padding:3px;gap:2px}.ud-cal__view-btn{padding:4px 12px;border:none;border-radius:6px;background:transparent;font-family:DM Sans,system-ui,sans-serif;font-size:12px;font-weight:500;color:var(--eu-muted, #6b7585);cursor:pointer;transition:background .15s,color .15s}.ud-cal__view-btn--active{background:var(--eu-navy, #1b2535);color:#fff}.ud-cal__view-btn:not(.ud-cal__view-btn--active):hover{background:#1b253514;color:var(--eu-text, #2a3548)}.ud-cal__week-scroll{overflow-y:auto;max-height:580px}.ud-cal__week-header{position:sticky;top:0;z-index:10;display:flex;background:#fafbfc;border-bottom:1px solid var(--eu-border-light, #e8eaef);flex-shrink:0}.ud-cal__time-gutter{width:52px;flex-shrink:0;border-right:1px solid var(--eu-border-light, #e8eaef)}.ud-cal__day-header{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:8px 4px;gap:2px;border-right:1px solid var(--eu-border-light, #e8eaef)}.ud-cal__day-header:last-child{border-right:none}.ud-cal__day-header--today{background:#1b25350a}.ud-cal__day-name{font-size:10px;font-weight:600;color:var(--eu-muted, #6b7585);text-transform:uppercase;letter-spacing:.06em}.ud-cal__day-num{font-size:15px;font-weight:600;color:var(--eu-text, #2a3548)}.ud-cal__day-num--today{display:flex;align-items:center;justify-content:center;width:26px;height:26px;background:var(--eu-navy, #1b2535);color:#fff;border-radius:50%;font-size:13px}.ud-cal__week-grid{display:flex}.ud-cal__time-col{width:52px;flex-shrink:0;border-right:1px solid var(--eu-border-light, #e8eaef)}.ud-cal__time-cell{height:56px;position:relative;display:flex;align-items:flex-start;justify-content:flex-end;padding:0 6px}.ud-cal__time-label{font-size:10px;color:var(--eu-muted, #9099a8);font-weight:500;white-space:nowrap;margin-top:-6px}.ud-cal__day-col{flex:1;position:relative;border-right:1px solid var(--eu-border-light, #e8eaef)}.ud-cal__day-col:last-child{border-right:none}.ud-cal__day-col--today{background:#1b253506}.ud-cal__grid-cell{height:56px;border-bottom:1px solid var(--eu-border-light, #e8eaef);transition:background .1s;cursor:pointer}.ud-cal__grid-cell--half{border-bottom-style:dashed;border-bottom-color:#d8dde673}.ud-cal__grid-cell:hover{background:#1b253508}.ud-cal__slot{position:absolute;left:3px;right:3px;border-left:3px solid;border-radius:6px;padding:3px 6px;display:flex;flex-direction:column;gap:1px;overflow:hidden;z-index:1;transition:filter .15s,transform .1s}.ud-cal__slot--clickable{cursor:grab}.ud-cal__slot--clickable:hover{filter:brightness(.93);transform:translate(1px)}.ud-cal__slot-inner{display:flex;align-items:center;gap:3px;overflow:hidden;min-width:0}.ud-cal__slot-title{font-size:11px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.2;min-width:0}.ud-cal__slot-time{font-size:10px;opacity:.8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.2}.ud-cal__slot--dragging{opacity:.3;pointer-events:none}.ud-cal__slot--drag-preview{pointer-events:none;opacity:.75;border-left-style:dashed;border-top:1px dashed currentColor;z-index:2}.ud-cal__slot--booked{background-image:repeating-linear-gradient(-45deg,transparent,transparent 4px,rgba(229,57,53,.07) 4px,rgba(229,57,53,.07) 8px)!important;cursor:not-allowed;border-top:1px solid rgba(229,57,53,.18)}.ud-cal__slot--booked:hover{filter:none!important;transform:none!important}.ud-cal__slot--booked .ud-cal__slot-title{text-decoration:line-through;text-decoration-color:#c6282866;text-decoration-thickness:1px;opacity:.9}.ud-cal__slot-lock{font-size:10px!important;width:10px!important;height:10px!important;line-height:10px!important;flex-shrink:0;opacity:.7}.ud-cal__slot-booked-by{font-size:9px;font-weight:500;opacity:.6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:.01em;margin-top:1px}.ud-cal__month{display:flex;flex-direction:column}.ud-cal__month-header{display:grid;grid-template-columns:repeat(7,1fr);border-bottom:1px solid var(--eu-border-light, #e8eaef);background:#fafbfc}.ud-cal__month-day-name{text-align:center;padding:8px 4px;font-size:10px;font-weight:600;color:var(--eu-muted, #6b7585);text-transform:uppercase;letter-spacing:.06em;border-right:1px solid var(--eu-border-light, #e8eaef)}.ud-cal__month-day-name:last-child{border-right:none}.ud-cal__month-grid{display:grid;grid-template-columns:repeat(7,1fr)}.ud-cal__month-cell{min-height:80px;padding:6px;border-right:1px solid var(--eu-border-light, #e8eaef);border-bottom:1px solid var(--eu-border-light, #e8eaef);cursor:pointer;transition:background .12s}.ud-cal__month-cell:nth-child(7n){border-right:none}.ud-cal__month-cell:hover{background:#1b253508}.ud-cal__month-cell--today{background:#1b25350a}.ud-cal__month-cell--other{opacity:.4}.ud-cal__month-num{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;font-size:12px;font-weight:600;color:var(--eu-text, #2a3548);border-radius:50%}.ud-cal__month-num--today{background:var(--eu-navy, #1b2535);color:#fff}.ud-cal__month-dots{display:flex;flex-wrap:wrap;align-items:center;gap:3px;margin-top:4px}.ud-cal__month-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.ud-cal__month-more{font-size:9px;font-weight:600;color:var(--eu-muted, #6b7585)}\n"] }]
3643
+ }], ctorParameters: () => [], propDecorators: { onDocMouseMove: [{
3644
+ type: HostListener,
3645
+ args: ['document:mousemove', ['$event']]
3646
+ }], onDocMouseUp: [{
3647
+ type: HostListener,
3648
+ args: ['document:mouseup', ['$event']]
3649
+ }] } });
3650
+
3123
3651
  /**
3124
3652
  * Segmented button toggle group. Works with [(ngModel)] and reactive forms.
3125
3653
  *
@@ -3408,5 +3936,5 @@ const generateTimeOptions = (start, end, intervalMinutes = 5) => {
3408
3936
  * Generated bundle index. Do not edit.
3409
3937
  */
3410
3938
 
3411
- export { ApplicationStatus, AutocompleteComponent, CapitalizePipe, CarouselComponent, CustomInputComponent, CustomSnackbarComponent, CustomTableComponent, DateInputComponent, DateOperator, DateRangeInputComponent, DynamicComponentComponent, EditViewComponent, EditViewSectionDirective, FeatureFlagKey, FileInputComponent, FilterType, IconColor, KpiComponent, KpiDataType, KpiPillType, KpiProgressBarType, KpiVariant, LoadingStatus, ModalComponent, ModalInputType, MultiSelectComponent, NumberOperator, PhoneInputComponent, PillComponent, PillToggleComponent, PluralizePipe, ProgressBarComponent, SafePipe, SingularPipe, SnackbarType, StringOperator, SummaryViewComponent, TableDisplayColumnType, TabsComponent, TelInputComponent, TextInputComponent, TextareaComponent, TimePickerComponent, ToObservablePipe, ToggleComponent, ToggleOptionComponent, TranslateWrapperService, UdButtonComponent, UdButtonToggleComponent, UdPreviewContainerComponent, UdStepContentDirective, UdStepperComponent, capitalize, formatLocalDate, formatLocalDateTime, formatLocalDateTimeLongForm, formatLocalTime, formatLocalTimeWithMinutes, formatLocalTimeWithMinutesAmPm, formatMonthYear, formatPhoneNumber, formatStringDate, formatStringDateTime, generateTimeOptions, inListValidator, parseLocalDate, pluralize, spaceCase, updateArray, withLoadingState };
3939
+ export { ApplicationStatus, AutocompleteComponent, CalendarComponent, CapitalizePipe, CarouselComponent, ChipInputComponent, CustomInputComponent, CustomSnackbarComponent, CustomTableComponent, DateInputComponent, DateOperator, DateRangeInputComponent, DynamicComponentComponent, EditViewComponent, EditViewSectionDirective, FeatureFlagKey, FileInputComponent, FilterType, IconColor, KpiComponent, KpiDataType, KpiPillType, KpiProgressBarType, KpiVariant, LoadingStatus, ModalComponent, ModalInputType, MultiSelectComponent, NumberOperator, PhoneInputComponent, PillComponent, PillToggleComponent, PluralizePipe, ProgressBarComponent, SafePipe, SingularPipe, SnackbarType, StringOperator, SummaryViewComponent, TableDisplayColumnType, TabsComponent, TelInputComponent, TextInputComponent, TextareaComponent, TimePickerComponent, ToObservablePipe, ToggleComponent, ToggleOptionComponent, TranslateWrapperService, UdButtonComponent, UdButtonToggleComponent, UdPreviewContainerComponent, UdStepContentDirective, UdStepperComponent, capitalize, formatLocalDate, formatLocalDateTime, formatLocalDateTimeLongForm, formatLocalTime, formatLocalTimeWithMinutes, formatLocalTimeWithMinutesAmPm, formatMonthYear, formatPhoneNumber, formatStringDate, formatStringDateTime, generateTimeOptions, inListValidator, parseLocalDate, pluralize, spaceCase, updateArray, withLoadingState };
3412
3940
  //# sourceMappingURL=ud-components.mjs.map