ud-components 0.5.15 → 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.
- package/fesm2022/ud-components.mjs +577 -49
- package/fesm2022/ud-components.mjs.map +1 -1
- package/lib/calendar/calendar.component.d.ts +81 -0
- package/lib/calendar/calendar.interface.d.ts +11 -0
- package/lib/carousel/carousel.component.d.ts +15 -3
- package/lib/form-fields/chip-input/chip-input.component.d.ts +23 -0
- package/lib/form-fields/pill-toggle/pill-toggle.component.d.ts +2 -2
- package/lib/form-fields/time-picker/time-picker.component.d.ts +12 -33
- package/lib/modal/modal.component.d.ts +1 -0
- package/package.json +2 -26
- package/public-api.d.ts +3 -0
|
@@ -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$
|
|
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
|
|
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
|
|
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
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
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['
|
|
2274
|
-
this.
|
|
2291
|
+
if (changes['intervalMinutes'] || changes['options']) {
|
|
2292
|
+
this.timeOptions = this.buildOptions();
|
|
2275
2293
|
}
|
|
2276
2294
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
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: "
|
|
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: [
|
|
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$
|
|
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,
|
|
@@ -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
|