ud-components 0.5.22 → 0.5.24
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.
|
@@ -2543,6 +2543,12 @@ class ModalComponent {
|
|
|
2543
2543
|
this.bodyText = this.data.bodyText;
|
|
2544
2544
|
if (this.data.showClose != undefined)
|
|
2545
2545
|
this.showClose = this.data.showClose;
|
|
2546
|
+
// Let dialog-mode consumers override the action labels too — needed for
|
|
2547
|
+
// contextual flows (e.g. "Reschedule" instead of the default "Save").
|
|
2548
|
+
if (this.data.confirmLabel != undefined)
|
|
2549
|
+
this.confirmLabel = this.data.confirmLabel;
|
|
2550
|
+
if (this.data.cancelLabel != undefined)
|
|
2551
|
+
this.cancelLabel = this.data.cancelLabel;
|
|
2546
2552
|
}
|
|
2547
2553
|
onDelete() {
|
|
2548
2554
|
this.data.delete?.();
|
|
@@ -3291,6 +3297,19 @@ class CalendarComponent {
|
|
|
3291
3297
|
* or deep links that should land on a specific day.
|
|
3292
3298
|
*/
|
|
3293
3299
|
defaultDate = input(null);
|
|
3300
|
+
/**
|
|
3301
|
+
* When set, the "Add slot" modal flips into reschedule mode:
|
|
3302
|
+
* • title becomes "Move appointment"
|
|
3303
|
+
* • a lede shows the original time being moved away from
|
|
3304
|
+
* • the user picker auto-selects the lone bookable user (or `userId` if
|
|
3305
|
+
* provided) so the admin doesn't have to choose from a list of one
|
|
3306
|
+
* • Booked defaults to true (a reschedule keeps a booking, not a fresh slot)
|
|
3307
|
+
* • the confirm button reads "Reschedule" instead of "Save"
|
|
3308
|
+
*
|
|
3309
|
+
* The wire-level event is still `slotAdded` — consumers route it to their
|
|
3310
|
+
* reschedule API based on their own context (no extra output needed).
|
|
3311
|
+
*/
|
|
3312
|
+
rescheduleContext = input(null);
|
|
3294
3313
|
slotAdded = output();
|
|
3295
3314
|
slotUpdated = output();
|
|
3296
3315
|
slotRemoved = output();
|
|
@@ -3598,6 +3617,19 @@ class CalendarComponent {
|
|
|
3598
3617
|
formatSlotTime(slot) {
|
|
3599
3618
|
return `${this.formatTime(slot.start)} – ${this.formatTime(slot.end)}`;
|
|
3600
3619
|
}
|
|
3620
|
+
/**
|
|
3621
|
+
* Pretty range like "Mon Jun 8 · 2:00 – 2:30 PM". Used by reschedule-mode's
|
|
3622
|
+
* "Moving from …" lede so the admin sees where the appointment is coming
|
|
3623
|
+
* from while picking the new time.
|
|
3624
|
+
*/
|
|
3625
|
+
formatMomentRange(start, end) {
|
|
3626
|
+
const dayLabel = start.toLocaleDateString(undefined, {
|
|
3627
|
+
weekday: 'short',
|
|
3628
|
+
month: 'short',
|
|
3629
|
+
day: 'numeric',
|
|
3630
|
+
});
|
|
3631
|
+
return `${dayLabel} · ${this.formatTime(start)} – ${this.formatTime(end)}`;
|
|
3632
|
+
}
|
|
3601
3633
|
isToday(day) {
|
|
3602
3634
|
return this.isSameDay(day, new Date());
|
|
3603
3635
|
}
|
|
@@ -3782,16 +3814,31 @@ class CalendarComponent {
|
|
|
3782
3814
|
}
|
|
3783
3815
|
const endDate = new Date(startDate.getTime() + this.slotDuration() * 60000);
|
|
3784
3816
|
const base = new Date(startDate);
|
|
3817
|
+
// Reschedule-mode wiring: pre-pick the target user, default to Booked,
|
|
3818
|
+
// and prepare the contextual modal copy. In normal "add slot" mode all of
|
|
3819
|
+
// this collapses back to the defaults.
|
|
3820
|
+
const reschedule = this.rescheduleContext();
|
|
3821
|
+
const presetUserId = reschedule
|
|
3822
|
+
? reschedule.userId ?? (this.bookableUsers().length === 1 ? this.bookableUsers()[0].value : '')
|
|
3823
|
+
: '';
|
|
3824
|
+
const presetBooked = !!reschedule;
|
|
3785
3825
|
const form = this.buildForm({
|
|
3786
3826
|
date: new Date(base),
|
|
3787
3827
|
startTime: startDate,
|
|
3788
3828
|
endTime: endDate,
|
|
3829
|
+
bookedBy: presetUserId,
|
|
3830
|
+
booked: presetBooked,
|
|
3831
|
+
title: reschedule?.defaultTitle ?? '',
|
|
3789
3832
|
});
|
|
3790
3833
|
const ref = this.dialog.open(ModalComponent, {
|
|
3791
3834
|
width: '480px',
|
|
3792
3835
|
data: {
|
|
3793
|
-
eyebrow: 'Calendar',
|
|
3794
|
-
title: 'Add time slot',
|
|
3836
|
+
eyebrow: reschedule ? 'Reschedule' : 'Calendar',
|
|
3837
|
+
title: reschedule ? 'Move appointment' : 'Add time slot',
|
|
3838
|
+
lede: reschedule
|
|
3839
|
+
? `Moving from ${this.formatMomentRange(reschedule.originalStart, reschedule.originalEnd)}`
|
|
3840
|
+
: undefined,
|
|
3841
|
+
confirmLabel: reschedule ? 'actions.reschedule' : undefined,
|
|
3795
3842
|
formGroup: form,
|
|
3796
3843
|
forms: this.slotForms(),
|
|
3797
3844
|
formErrors: this.slotFormErrors,
|
|
@@ -4017,7 +4064,7 @@ class CalendarComponent {
|
|
|
4017
4064
|
return `rgba(${r},${g},${b},${alpha})`;
|
|
4018
4065
|
}
|
|
4019
4066
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: CalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4020
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", 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 }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, bookableUsers: { classPropertyName: "bookableUsers", publicName: "bookableUsers", isSignal: true, isRequired: false, transformFunction: null }, minSlotStart: { classPropertyName: "minSlotStart", publicName: "minSlotStart", isSignal: true, isRequired: false, transformFunction: null }, defaultDate: { classPropertyName: "defaultDate", publicName: "defaultDate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { slotAdded: "slotAdded", slotUpdated: "slotUpdated", slotRemoved: "slotRemoved", slotBooked: "slotBooked", slotBlocked: "slotBlocked" }, host: { listeners: { "document:mousemove": "onDocMouseMove($event)", "document:mouseup": "onDocMouseUp($event)" } }, viewQueries: [{ propertyName: "hoverCardTpl", first: true, predicate: ["hoverCard"], descendants: true, isSignal: true }], 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\" [style.max-height]=\"maxHeight()\">\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 [class.ud-cal__grid-cell--disabled]=\"isCellDisabled(day, ts.hour, ts.minute)\"\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--' + slotDensity(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.left.%]=\"slotLeft(slot)\"\n [style.width.%]=\"slotWidth(slot)\"\n [style.background]=\"slotBg(slot)\"\n [style.color]=\"slotTextColor(slot)\"\n [style.border-color]=\"slotBorderColor(slot)\"\n (mouseenter)=\"openHoverCard(slot, $event.currentTarget)\"\n (mouseleave)=\"closeHoverCard()\"\n (mousedown)=\"onSlotMouseDown($event, slot)\">\n <div class=\"ud-cal__slot-inner\">\n @if (slot.booked) {\n <mat-icon class=\"ud-cal__slot-lock\">person</mat-icon>\n }\n @if (slot.title) {\n <span class=\"ud-cal__slot-title\">{{ slot.title }}</span>\n } @else if (slotDensity(slot) === 'compact') {\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n }\n </div>\n @if (slotDensity(slot) !== 'compact') {\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n }\n @if (slotDensity(slot) === 'comfortable' && 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 [class.ud-cal__month-cell--disabled]=\"isDayDisabled(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\" [style.max-height]=\"maxHeight()\">\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 [class.ud-cal__grid-cell--disabled]=\"isCellDisabled(navDate(), ts.hour, ts.minute)\"\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--' + slotDensity(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.left.%]=\"slotLeft(slot)\"\n [style.width.%]=\"slotWidth(slot)\"\n [style.background]=\"slotBg(slot)\"\n [style.color]=\"slotTextColor(slot)\"\n [style.border-color]=\"slotBorderColor(slot)\"\n (mouseenter)=\"openHoverCard(slot, $event.currentTarget)\"\n (mouseleave)=\"closeHoverCard()\"\n (mousedown)=\"onSlotMouseDown($event, slot)\">\n <div class=\"ud-cal__slot-inner\">\n @if (slot.booked) {\n <mat-icon class=\"ud-cal__slot-lock\">person</mat-icon>\n }\n @if (slot.title) {\n <span class=\"ud-cal__slot-title\">{{ slot.title }}</span>\n } @else if (slotDensity(slot) === 'compact') {\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n }\n </div>\n @if (slotDensity(slot) !== 'compact') {\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n }\n @if (slotDensity(slot) === 'comfortable' && 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<!-- \u2500\u2500 Rich hover card (rendered in a CDK overlay) \u2500\u2500\u2500\u2500\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<ng-template #hoverCard>\n @if (hoverSlot(); as s) {\n <div\n class=\"ud-cal__hovercard\"\n [class.ud-cal__hovercard--booked]=\"s.booked\"\n [style.--hc-accent]=\"slotBorderColor(s)\"\n (mouseenter)=\"cancelHoverClose()\"\n (mouseleave)=\"closeHoverCard()\">\n <span class=\"ud-cal__hovercard-bar\"></span>\n <div class=\"ud-cal__hovercard-body\">\n <div class=\"ud-cal__hovercard-head\">\n <span class=\"ud-cal__hovercard-title\">{{ s.title || 'Untitled slot' }}</span>\n <span class=\"ud-cal__hovercard-pill\" [class.ud-cal__hovercard-pill--booked]=\"s.booked\">\n <span class=\"ud-cal__hovercard-pill-dot\"></span>\n {{ s.booked ? 'Booked' : 'Available' }}\n </span>\n </div>\n\n <div class=\"ud-cal__hovercard-row\">\n <mat-icon class=\"ud-cal__hovercard-ico\">schedule</mat-icon>\n <span class=\"ud-cal__hovercard-time\">{{ formatSlotTime(s) }}</span>\n <span class=\"ud-cal__hovercard-dur\">{{ slotDurationLabel(s) }}</span>\n </div>\n\n @if (s.booked && s.bookedBy) {\n <div class=\"ud-cal__hovercard-row ud-cal__hovercard-person\">\n <span class=\"ud-cal__hovercard-avatar\" [style.background]=\"slotBorderColor(s)\">\n {{ slotInitials(s.bookedBy) }}\n </span>\n <span class=\"ud-cal__hovercard-person-name\">{{ s.bookedBy }}</span>\n </div>\n }\n </div>\n </div>\n }\n</ng-template>\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__grid-cell--disabled{cursor:not-allowed;background:repeating-linear-gradient(135deg,#1b253508,#1b253508 6px,#1b25350f 6px 12px)}.ud-cal__grid-cell--disabled:hover{background:repeating-linear-gradient(135deg,#1b253508,#1b253508 6px,#1b25350f 6px 12px)}.ud-cal__slot{position:absolute;margin-left:2px;box-sizing:border-box;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--cozy{padding:2px 6px;gap:0}.ud-cal__slot--compact{padding:1px 6px;gap:0;flex-direction:row;align-items:center;justify-content:flex-start}.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{left:3px;right:3px;margin-left:0;pointer-events:none;opacity:.75;border-left-style:dashed;border-top:1px dashed currentColor;z-index:2}.ud-cal__slot--booked{border-left-width:4px;box-shadow:inset 0 0 0 1px #1b253514}.ud-cal__slot--booked .ud-cal__slot-title{font-weight:600}.ud-cal__slot:not(.ud-cal__slot--booked){border-left-style:dashed}.ud-cal__slot-lock{font-size:11px!important;width:11px!important;height:11px!important;line-height:11px!important;flex-shrink:0;opacity:.75}.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-cell--disabled{cursor:not-allowed;color:#1b253559;background:repeating-linear-gradient(135deg,#1b253505,#1b253505 6px,#1b25350d 6px 12px)}.ud-cal__month-cell--disabled:hover{background:repeating-linear-gradient(135deg,#1b253505,#1b253505 6px,#1b25350d 6px 12px)}.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)}.ud-cal__hovercard{--hc-accent: var(--eu-navy, #1b2535);position:relative;display:flex;min-width:208px;max-width:288px;background:#fff;border:1px solid var(--eu-border-light, #e8eaef);border-radius:12px;overflow:hidden;font-family:DM Sans,system-ui,sans-serif;box-shadow:0 1px 2px #1b25350d,0 14px 30px -10px #1b25353d;transform-origin:top left;animation:ud-cal-hovercard-in .17s cubic-bezier(.16,1,.3,1) both}.ud-cal__hovercard:after{content:\"\";position:absolute;inset:0;pointer-events:none;background:linear-gradient(135deg,color-mix(in srgb,var(--hc-accent) 6%,transparent),transparent 55%)}@keyframes ud-cal-hovercard-in{0%{opacity:0;transform:translateY(5px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.ud-cal__hovercard-bar{width:4px;flex-shrink:0;background:var(--hc-accent)}.ud-cal__hovercard-body{position:relative;z-index:1;flex:1;min-width:0;padding:12px 14px 13px;display:flex;flex-direction:column;gap:9px}.ud-cal__hovercard-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.ud-cal__hovercard-title{font-size:13.5px;font-weight:600;letter-spacing:-.01em;line-height:1.3;color:var(--eu-text, #2a3548);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.ud-cal__hovercard-pill{display:inline-flex;align-items:center;gap:5px;flex-shrink:0;padding:3px 9px 3px 7px;border-radius:999px;font-size:9.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;background:#1b253512;color:#1b253599}.ud-cal__hovercard-pill--booked{background:#1b25351f;color:var(--eu-navy)}.ud-cal__hovercard-pill-dot{width:5px;height:5px;border-radius:50%;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 22%,transparent)}.ud-cal__hovercard-row{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:500;color:var(--eu-muted, #6b7585)}.ud-cal__hovercard-ico{font-size:16px!important;width:16px!important;height:16px!important;line-height:16px!important;color:var(--hc-accent);opacity:.9}.ud-cal__hovercard-time{color:var(--eu-text, #2a3548);font-weight:600;font-variant-numeric:tabular-nums}.ud-cal__hovercard-dur{margin-left:auto;font-size:10px;font-weight:600;color:var(--eu-muted, #6b7585);background:var(--eu-bg, #f4f5f7);border:1px solid var(--eu-border-light, #e8eaef);padding:1px 8px;border-radius:6px;white-space:nowrap}.ud-cal__hovercard-person{padding-top:8px;border-top:1px dashed var(--eu-border-light, #e8eaef)}.ud-cal__hovercard-avatar{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;background:var(--hc-accent);color:#fff;font-size:9px;font-weight:700;letter-spacing:.03em;flex-shrink:0}.ud-cal__hovercard-person-name{color:var(--eu-text, #2a3548);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\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"] }] });
|
|
4067
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", 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 }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, bookableUsers: { classPropertyName: "bookableUsers", publicName: "bookableUsers", isSignal: true, isRequired: false, transformFunction: null }, minSlotStart: { classPropertyName: "minSlotStart", publicName: "minSlotStart", isSignal: true, isRequired: false, transformFunction: null }, defaultDate: { classPropertyName: "defaultDate", publicName: "defaultDate", isSignal: true, isRequired: false, transformFunction: null }, rescheduleContext: { classPropertyName: "rescheduleContext", publicName: "rescheduleContext", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { slotAdded: "slotAdded", slotUpdated: "slotUpdated", slotRemoved: "slotRemoved", slotBooked: "slotBooked", slotBlocked: "slotBlocked" }, host: { listeners: { "document:mousemove": "onDocMouseMove($event)", "document:mouseup": "onDocMouseUp($event)" } }, viewQueries: [{ propertyName: "hoverCardTpl", first: true, predicate: ["hoverCard"], descendants: true, isSignal: true }], 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\" [style.max-height]=\"maxHeight()\">\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 [class.ud-cal__grid-cell--disabled]=\"isCellDisabled(day, ts.hour, ts.minute)\"\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--' + slotDensity(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.left.%]=\"slotLeft(slot)\"\n [style.width.%]=\"slotWidth(slot)\"\n [style.background]=\"slotBg(slot)\"\n [style.color]=\"slotTextColor(slot)\"\n [style.border-color]=\"slotBorderColor(slot)\"\n (mouseenter)=\"openHoverCard(slot, $event.currentTarget)\"\n (mouseleave)=\"closeHoverCard()\"\n (mousedown)=\"onSlotMouseDown($event, slot)\">\n <div class=\"ud-cal__slot-inner\">\n @if (slot.booked) {\n <mat-icon class=\"ud-cal__slot-lock\">person</mat-icon>\n }\n @if (slot.title) {\n <span class=\"ud-cal__slot-title\">{{ slot.title }}</span>\n } @else if (slotDensity(slot) === 'compact') {\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n }\n </div>\n @if (slotDensity(slot) !== 'compact') {\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n }\n @if (slotDensity(slot) === 'comfortable' && 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 [class.ud-cal__month-cell--disabled]=\"isDayDisabled(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\" [style.max-height]=\"maxHeight()\">\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 [class.ud-cal__grid-cell--disabled]=\"isCellDisabled(navDate(), ts.hour, ts.minute)\"\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--' + slotDensity(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.left.%]=\"slotLeft(slot)\"\n [style.width.%]=\"slotWidth(slot)\"\n [style.background]=\"slotBg(slot)\"\n [style.color]=\"slotTextColor(slot)\"\n [style.border-color]=\"slotBorderColor(slot)\"\n (mouseenter)=\"openHoverCard(slot, $event.currentTarget)\"\n (mouseleave)=\"closeHoverCard()\"\n (mousedown)=\"onSlotMouseDown($event, slot)\">\n <div class=\"ud-cal__slot-inner\">\n @if (slot.booked) {\n <mat-icon class=\"ud-cal__slot-lock\">person</mat-icon>\n }\n @if (slot.title) {\n <span class=\"ud-cal__slot-title\">{{ slot.title }}</span>\n } @else if (slotDensity(slot) === 'compact') {\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n }\n </div>\n @if (slotDensity(slot) !== 'compact') {\n <span class=\"ud-cal__slot-time\">{{ formatSlotTime(slot) }}</span>\n }\n @if (slotDensity(slot) === 'comfortable' && 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<!-- \u2500\u2500 Rich hover card (rendered in a CDK overlay) \u2500\u2500\u2500\u2500\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<ng-template #hoverCard>\n @if (hoverSlot(); as s) {\n <div\n class=\"ud-cal__hovercard\"\n [class.ud-cal__hovercard--booked]=\"s.booked\"\n [style.--hc-accent]=\"slotBorderColor(s)\"\n (mouseenter)=\"cancelHoverClose()\"\n (mouseleave)=\"closeHoverCard()\">\n <span class=\"ud-cal__hovercard-bar\"></span>\n <div class=\"ud-cal__hovercard-body\">\n <div class=\"ud-cal__hovercard-head\">\n <span class=\"ud-cal__hovercard-title\">{{ s.title || 'Untitled slot' }}</span>\n <span class=\"ud-cal__hovercard-pill\" [class.ud-cal__hovercard-pill--booked]=\"s.booked\">\n <span class=\"ud-cal__hovercard-pill-dot\"></span>\n {{ s.booked ? 'Booked' : 'Available' }}\n </span>\n </div>\n\n <div class=\"ud-cal__hovercard-row\">\n <mat-icon class=\"ud-cal__hovercard-ico\">schedule</mat-icon>\n <span class=\"ud-cal__hovercard-time\">{{ formatSlotTime(s) }}</span>\n <span class=\"ud-cal__hovercard-dur\">{{ slotDurationLabel(s) }}</span>\n </div>\n\n @if (s.booked && s.bookedBy) {\n <div class=\"ud-cal__hovercard-row ud-cal__hovercard-person\">\n <span class=\"ud-cal__hovercard-avatar\" [style.background]=\"slotBorderColor(s)\">\n {{ slotInitials(s.bookedBy) }}\n </span>\n <span class=\"ud-cal__hovercard-person-name\">{{ s.bookedBy }}</span>\n </div>\n }\n </div>\n </div>\n }\n</ng-template>\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__grid-cell--disabled{cursor:not-allowed;background:repeating-linear-gradient(135deg,#1b253508,#1b253508 6px,#1b25350f 6px 12px)}.ud-cal__grid-cell--disabled:hover{background:repeating-linear-gradient(135deg,#1b253508,#1b253508 6px,#1b25350f 6px 12px)}.ud-cal__slot{position:absolute;margin-left:2px;box-sizing:border-box;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--cozy{padding:2px 6px;gap:0}.ud-cal__slot--compact{padding:1px 6px;gap:0;flex-direction:row;align-items:center;justify-content:flex-start}.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{left:3px;right:3px;margin-left:0;pointer-events:none;opacity:.75;border-left-style:dashed;border-top:1px dashed currentColor;z-index:2}.ud-cal__slot--booked{border-left-width:4px;box-shadow:inset 0 0 0 1px #1b253514}.ud-cal__slot--booked .ud-cal__slot-title{font-weight:600}.ud-cal__slot:not(.ud-cal__slot--booked){border-left-style:dashed}.ud-cal__slot-lock{font-size:11px!important;width:11px!important;height:11px!important;line-height:11px!important;flex-shrink:0;opacity:.75}.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-cell--disabled{cursor:not-allowed;color:#1b253559;background:repeating-linear-gradient(135deg,#1b253505,#1b253505 6px,#1b25350d 6px 12px)}.ud-cal__month-cell--disabled:hover{background:repeating-linear-gradient(135deg,#1b253505,#1b253505 6px,#1b25350d 6px 12px)}.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)}.ud-cal__hovercard{--hc-accent: var(--eu-navy, #1b2535);position:relative;display:flex;min-width:208px;max-width:288px;background:#fff;border:1px solid var(--eu-border-light, #e8eaef);border-radius:12px;overflow:hidden;font-family:DM Sans,system-ui,sans-serif;box-shadow:0 1px 2px #1b25350d,0 14px 30px -10px #1b25353d;transform-origin:top left;animation:ud-cal-hovercard-in .17s cubic-bezier(.16,1,.3,1) both}.ud-cal__hovercard:after{content:\"\";position:absolute;inset:0;pointer-events:none;background:linear-gradient(135deg,color-mix(in srgb,var(--hc-accent) 6%,transparent),transparent 55%)}@keyframes ud-cal-hovercard-in{0%{opacity:0;transform:translateY(5px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.ud-cal__hovercard-bar{width:4px;flex-shrink:0;background:var(--hc-accent)}.ud-cal__hovercard-body{position:relative;z-index:1;flex:1;min-width:0;padding:12px 14px 13px;display:flex;flex-direction:column;gap:9px}.ud-cal__hovercard-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.ud-cal__hovercard-title{font-size:13.5px;font-weight:600;letter-spacing:-.01em;line-height:1.3;color:var(--eu-text, #2a3548);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.ud-cal__hovercard-pill{display:inline-flex;align-items:center;gap:5px;flex-shrink:0;padding:3px 9px 3px 7px;border-radius:999px;font-size:9.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;background:#1b253512;color:#1b253599}.ud-cal__hovercard-pill--booked{background:#1b25351f;color:var(--eu-navy)}.ud-cal__hovercard-pill-dot{width:5px;height:5px;border-radius:50%;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 22%,transparent)}.ud-cal__hovercard-row{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:500;color:var(--eu-muted, #6b7585)}.ud-cal__hovercard-ico{font-size:16px!important;width:16px!important;height:16px!important;line-height:16px!important;color:var(--hc-accent);opacity:.9}.ud-cal__hovercard-time{color:var(--eu-text, #2a3548);font-weight:600;font-variant-numeric:tabular-nums}.ud-cal__hovercard-dur{margin-left:auto;font-size:10px;font-weight:600;color:var(--eu-muted, #6b7585);background:var(--eu-bg, #f4f5f7);border:1px solid var(--eu-border-light, #e8eaef);padding:1px 8px;border-radius:6px;white-space:nowrap}.ud-cal__hovercard-person{padding-top:8px;border-top:1px dashed var(--eu-border-light, #e8eaef)}.ud-cal__hovercard-avatar{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;background:var(--hc-accent);color:#fff;font-size:9px;font-weight:700;letter-spacing:.03em;flex-shrink:0}.ud-cal__hovercard-person-name{color:var(--eu-text, #2a3548);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\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"] }] });
|
|
4021
4068
|
}
|
|
4022
4069
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: CalendarComponent, decorators: [{
|
|
4023
4070
|
type: Component,
|