mn-angular-lib 1.0.118 → 1.0.120
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.
|
@@ -9,7 +9,7 @@ import * as i1$1 from '@angular/forms';
|
|
|
9
9
|
import { NgControl, Validators, FormsModule, NG_VALUE_ACCESSOR, FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
|
10
10
|
import { HttpClient, HttpErrorResponse, HttpStatusCode, HttpParams } from '@angular/common/http';
|
|
11
11
|
import JSON5 from 'json5';
|
|
12
|
-
import { LucideFile, LucideImagePlus, LucideTrash2, LucideUpload, LucideX, LucideChevronLeft, LucideChevronRight, LucideArrowLeft, LucideArrowRight, LucideCheck, LucideFilter, LucideFunnel, LucideDynamicIcon } from '@lucide/angular';
|
|
12
|
+
import { LucideFile, LucideImagePlus, LucideTrash2, LucideUpload, LucideX, LucideCalendarDays, LucideChevronLeft, LucideChevronRight, LucideArrowLeft, LucideArrowRight, LucideCheck, LucideFilter, LucideFunnel, LucideDynamicIcon } from '@lucide/angular';
|
|
13
13
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
14
14
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
15
15
|
|
|
@@ -229,6 +229,9 @@ const mnButtonVariants = tv({
|
|
|
229
229
|
outline: 'bg-transparent border',
|
|
230
230
|
text: 'bg-transparent',
|
|
231
231
|
textUnderline: 'bg-transparent underline underline-offset-2',
|
|
232
|
+
// Transparent button that only reveals a subtle surface on hover — the idiomatic
|
|
233
|
+
// icon-button look. Colors resolved via compoundVariants.
|
|
234
|
+
ghost: 'bg-transparent',
|
|
232
235
|
},
|
|
233
236
|
// Intentionally empty; resolved via compoundVariants
|
|
234
237
|
color: {
|
|
@@ -251,6 +254,14 @@ const mnButtonVariants = tv({
|
|
|
251
254
|
three_xl: 'rounded-3xl',
|
|
252
255
|
four_xl: 'rounded-4xl',
|
|
253
256
|
},
|
|
257
|
+
// Icon-button shape. `circle`/`square` drop the text padding and make the button a
|
|
258
|
+
// fixed square sized off `size` (see compoundVariants), overriding `borderRadius`.
|
|
259
|
+
// `default` leaves a normal text button untouched.
|
|
260
|
+
shape: {
|
|
261
|
+
default: '',
|
|
262
|
+
circle: 'p-0 shrink-0 rounded-full',
|
|
263
|
+
square: 'p-0 shrink-0 rounded-md',
|
|
264
|
+
},
|
|
254
265
|
disabled: {
|
|
255
266
|
true: 'opacity-50 pointer-events-none',
|
|
256
267
|
},
|
|
@@ -332,12 +343,36 @@ const mnButtonVariants = tv({
|
|
|
332
343
|
{ variant: 'textUnderline', color: 'success', hover: true, class: 'hover:bg-success/10' },
|
|
333
344
|
{ variant: 'textUnderline', color: 'accent', hover: true, class: 'hover:bg-accent/10' },
|
|
334
345
|
{ variant: 'textUnderline', color: 'gray', hover: true, class: 'hover:bg-base-content/10' },
|
|
346
|
+
// Ghost — base (transparent, colored text/icon)
|
|
347
|
+
{ variant: 'ghost', color: 'primary', class: 'text-primary' },
|
|
348
|
+
{ variant: 'ghost', color: 'secondary', class: 'text-neutral' },
|
|
349
|
+
{ variant: 'ghost', color: 'danger', class: 'text-error' },
|
|
350
|
+
{ variant: 'ghost', color: 'warning', class: 'text-warning' },
|
|
351
|
+
{ variant: 'ghost', color: 'success', class: 'text-success' },
|
|
352
|
+
{ variant: 'ghost', color: 'accent', class: 'text-accent' },
|
|
353
|
+
{ variant: 'ghost', color: 'gray', class: 'text-base-content' },
|
|
354
|
+
// Ghost — hover (reveal a subtle surface in the button's own hue)
|
|
355
|
+
{ variant: 'ghost', color: 'primary', hover: true, class: 'hover:bg-primary/10' },
|
|
356
|
+
{ variant: 'ghost', color: 'secondary', hover: true, class: 'hover:bg-neutral/10' },
|
|
357
|
+
{ variant: 'ghost', color: 'danger', hover: true, class: 'hover:bg-error/10' },
|
|
358
|
+
{ variant: 'ghost', color: 'warning', hover: true, class: 'hover:bg-warning/10' },
|
|
359
|
+
{ variant: 'ghost', color: 'success', hover: true, class: 'hover:bg-success/10' },
|
|
360
|
+
{ variant: 'ghost', color: 'accent', hover: true, class: 'hover:bg-accent/10' },
|
|
361
|
+
{ variant: 'ghost', color: 'gray', hover: true, class: 'hover:bg-base-content/10' },
|
|
362
|
+
// Icon-button square sizing — applies to both circle and square shapes, sized off `size`.
|
|
363
|
+
{ shape: 'circle', size: 'sm', class: 'h-8 w-8' },
|
|
364
|
+
{ shape: 'circle', size: 'md', class: 'h-10 w-10' },
|
|
365
|
+
{ shape: 'circle', size: 'lg', class: 'h-12 w-12' },
|
|
366
|
+
{ shape: 'square', size: 'sm', class: 'h-8 w-8' },
|
|
367
|
+
{ shape: 'square', size: 'md', class: 'h-10 w-10' },
|
|
368
|
+
{ shape: 'square', size: 'lg', class: 'h-12 w-12' },
|
|
335
369
|
],
|
|
336
370
|
defaultVariants: {
|
|
337
371
|
size: 'md',
|
|
338
372
|
variant: 'fill',
|
|
339
373
|
color: 'primary',
|
|
340
374
|
borderRadius: 'lg',
|
|
375
|
+
shape: 'default',
|
|
341
376
|
disabled: false,
|
|
342
377
|
loading: false,
|
|
343
378
|
wrap: false,
|
|
@@ -355,6 +390,7 @@ class MnButton {
|
|
|
355
390
|
variant: this.data.variant,
|
|
356
391
|
color: this.data.color,
|
|
357
392
|
borderRadius: this.data.borderRadius,
|
|
393
|
+
shape: this.data.shape,
|
|
358
394
|
disabled: this.data.disabled,
|
|
359
395
|
loading: this.data.loading,
|
|
360
396
|
wrap: this.data.wrap,
|
|
@@ -2889,7 +2925,35 @@ class MnDatetime {
|
|
|
2889
2925
|
get resolvedMode() {
|
|
2890
2926
|
return this.props.mode ?? 'datetime-local';
|
|
2891
2927
|
}
|
|
2928
|
+
/** Whether the control renders as an icon-only button rather than a full input. */
|
|
2929
|
+
get iconOnly() {
|
|
2930
|
+
return this.props.iconOnly === true;
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* Accessible name for the input. Prefers an explicit ariaLabel/label; for the
|
|
2934
|
+
* icon-only variant — which has no visible text — it falls back to the
|
|
2935
|
+
* placeholder so the button is never left unnamed.
|
|
2936
|
+
*/
|
|
2937
|
+
get resolvedAriaLabel() {
|
|
2938
|
+
const explicit = this.uiConfig.ariaLabel || this.uiConfig.label || this.props.label;
|
|
2939
|
+
if (explicit)
|
|
2940
|
+
return explicit;
|
|
2941
|
+
return this.iconOnly ? (this.uiConfig.placeholder || this.props.placeholder || null) : null;
|
|
2942
|
+
}
|
|
2943
|
+
/** Lucide icon size (px) tracking the field size, used only in the icon-only variant. */
|
|
2944
|
+
get iconSize() {
|
|
2945
|
+
if (this.props.size === 'sm')
|
|
2946
|
+
return 16;
|
|
2947
|
+
if (this.props.size === 'lg')
|
|
2948
|
+
return 20;
|
|
2949
|
+
return 18;
|
|
2950
|
+
}
|
|
2892
2951
|
get inputClasses() {
|
|
2952
|
+
// Icon-only: the input becomes a transparent click target filling the icon box,
|
|
2953
|
+
// which carries the visible styling. `iconBoxClasses` provides the box itself.
|
|
2954
|
+
if (this.iconOnly) {
|
|
2955
|
+
return 'absolute inset-0 h-full w-full cursor-pointer opacity-0';
|
|
2956
|
+
}
|
|
2893
2957
|
return mnDatetimeVariants({
|
|
2894
2958
|
size: this.props.size,
|
|
2895
2959
|
borderRadius: this.props.borderRadius,
|
|
@@ -2898,12 +2962,30 @@ class MnDatetime {
|
|
|
2898
2962
|
hover: this.props.hover,
|
|
2899
2963
|
});
|
|
2900
2964
|
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Classes for the icon-only box: the same border/background/radius/hover the full
|
|
2967
|
+
* input would wear (reused from the variant), made a positioning context for the
|
|
2968
|
+
* overlaid input, with a `focus-within` ring standing in for the transparent
|
|
2969
|
+
* input's own focus outline.
|
|
2970
|
+
*/
|
|
2971
|
+
get iconBoxClasses() {
|
|
2972
|
+
return [
|
|
2973
|
+
mnDatetimeVariants({
|
|
2974
|
+
size: this.props.size,
|
|
2975
|
+
borderRadius: this.props.borderRadius,
|
|
2976
|
+
shadow: this.props.shadow,
|
|
2977
|
+
hover: this.props.hover,
|
|
2978
|
+
}),
|
|
2979
|
+
'relative inline-flex items-center justify-center text-base-content/70',
|
|
2980
|
+
'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-primary',
|
|
2981
|
+
].join(' ');
|
|
2982
|
+
}
|
|
2901
2983
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnDatetime, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2902
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnDatetime, isStandalone: true, selector: "mn-lib-datetime", inputs: { props: "props" }, host: { properties: { "style.display": "props?.fullWidth ? 'block' : null", "style.width": "props?.fullWidth ? '100%' : null" } }, ngImport: i0, template: "<div class=\"flex flex-col h-full\" [class.is-fullwidth]=\"props.fullWidth\">\n @if (uiConfig.label || props.label) {\n <label class=\"pl-2 pb-1 flex flex-row gap-x-0.5! text-base!\" [attr.for]=\"resolvedId\">\n <p>{{ uiConfig.label || props.label }}</p>\n @if (isRequired()) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <input\n #dateInput\n [id]=\"resolvedId\"\n [attr.name]=\"resolvedName\"\n [type]=\"resolvedMode\"\n [attr.placeholder]=\"uiConfig.placeholder || props.placeholder || null\"\n [attr.aria-label]=\"
|
|
2984
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnDatetime, isStandalone: true, selector: "mn-lib-datetime", inputs: { props: "props" }, host: { properties: { "style.display": "props?.fullWidth ? 'block' : null", "style.width": "props?.fullWidth ? '100%' : null" } }, ngImport: i0, template: "<div class=\"flex flex-col h-full\" [class.is-fullwidth]=\"props.fullWidth\">\n @if (uiConfig.label || props.label) {\n <label class=\"pl-2 pb-1 flex flex-row gap-x-0.5! text-base!\" [attr.for]=\"resolvedId\">\n <p>{{ uiConfig.label || props.label }}</p>\n @if (isRequired()) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n @if (iconOnly) {\n <!--\n Icon-only variant: a bordered box holding a calendar icon, with the native\n input layered on top at zero opacity. The box carries the look (border,\n background, radius, hover, focus ring); the input stays a real, focusable\n form control that opens the picker on click. `focus-within` on the box\n surfaces the keyboard focus ring, since the input itself is transparent.\n -->\n <div [ngClass]=\"iconBoxClasses\">\n <svg [size]=\"iconSize\" lucideCalendarDays aria-hidden=\"true\" class=\"pointer-events-none\"></svg>\n <ng-container [ngTemplateOutlet]=\"inputTemplate\"></ng-container>\n </div>\n } @else {\n <ng-container [ngTemplateOutlet]=\"inputTemplate\"></ng-container>\n }\n\n @if (showError) {\n @if (props.showAllErrors) {\n <div class=\"flex flex-col gap-y-1\">\n @for (error of errorMessages; track $index) {\n <mn-error-message [errorMessage]=\"error\" [id]=\"resolvedId + '-' + $index\"></mn-error-message>\n }\n </div>\n } @else {\n @if (errorMessage !== null) {\n <mn-error-message [errorMessage]=\"errorMessage\" [id]=\"resolvedId\"></mn-error-message>\n }\n }\n }\n</div>\n\n<!-- Single input definition, rendered either bare or inside the icon box above,\n so both variants share exactly the same bindings and behaviour. -->\n<ng-template #inputTemplate>\n <input\n #dateInput\n [id]=\"resolvedId\"\n [attr.name]=\"resolvedName\"\n [type]=\"resolvedMode\"\n [attr.placeholder]=\"uiConfig.placeholder || props.placeholder || null\"\n [attr.aria-label]=\"resolvedAriaLabel\"\n [attr.aria-invalid]=\"showError || null\"\n [attr.aria-describedby]=\"showError ? resolvedId + '-error' : null\"\n [disabled]=\"isDisabled\"\n [attr.min]=\"props.min || null\"\n [attr.max]=\"props.max || null\"\n [attr.step]=\"props.step || null\"\n [value]=\"value ?? ''\"\n [ngClass]=\"inputClasses\"\n (click)=\"handleClick(dateInput)\"\n (input)=\"handleInput(($any($event.target)).value)\"\n (blur)=\"handleBlur()\"\n />\n</ng-template>\n", styles: ["input::-webkit-calendar-picker-indicator{cursor:pointer}@media(pointer:coarse){input[type=date],input[type=datetime-local],input[type=time],input[type=month],input[type=week]{-webkit-appearance:none;appearance:none;min-width:0;box-sizing:border-box}}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MnErrorMessage, selector: "mn-error-message", inputs: ["errorMessage", "id"] }, { kind: "component", type: LucideCalendarDays, selector: "svg[lucideCalendarDays]" }] });
|
|
2903
2985
|
}
|
|
2904
2986
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnDatetime, decorators: [{
|
|
2905
2987
|
type: Component,
|
|
2906
|
-
args: [{ selector: 'mn-lib-datetime', standalone: true, imports: [NgClass, MnErrorMessage], host: {
|
|
2988
|
+
args: [{ selector: 'mn-lib-datetime', standalone: true, imports: [NgClass, NgTemplateOutlet, MnErrorMessage, LucideCalendarDays], host: {
|
|
2907
2989
|
// Native date/time inputs have a platform-specific intrinsic width. Without an
|
|
2908
2990
|
// explicit host width the inline host collapses to that intrinsic size, so the
|
|
2909
2991
|
// input's `w-full` (width:100%) resolves against a content-sized box and fails to
|
|
@@ -2912,7 +2994,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
2912
2994
|
// when fullWidth is requested so 100% has something to fill.
|
|
2913
2995
|
'[style.display]': "props?.fullWidth ? 'block' : null",
|
|
2914
2996
|
'[style.width]': "props?.fullWidth ? '100%' : null",
|
|
2915
|
-
}, template: "<div class=\"flex flex-col h-full\" [class.is-fullwidth]=\"props.fullWidth\">\n @if (uiConfig.label || props.label) {\n <label class=\"pl-2 pb-1 flex flex-row gap-x-0.5! text-base!\" [attr.for]=\"resolvedId\">\n <p>{{ uiConfig.label || props.label }}</p>\n @if (isRequired()) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <input\n #dateInput\n [id]=\"resolvedId\"\n [attr.name]=\"resolvedName\"\n [type]=\"resolvedMode\"\n [attr.placeholder]=\"uiConfig.placeholder || props.placeholder || null\"\n [attr.aria-label]=\"
|
|
2997
|
+
}, template: "<div class=\"flex flex-col h-full\" [class.is-fullwidth]=\"props.fullWidth\">\n @if (uiConfig.label || props.label) {\n <label class=\"pl-2 pb-1 flex flex-row gap-x-0.5! text-base!\" [attr.for]=\"resolvedId\">\n <p>{{ uiConfig.label || props.label }}</p>\n @if (isRequired()) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n @if (iconOnly) {\n <!--\n Icon-only variant: a bordered box holding a calendar icon, with the native\n input layered on top at zero opacity. The box carries the look (border,\n background, radius, hover, focus ring); the input stays a real, focusable\n form control that opens the picker on click. `focus-within` on the box\n surfaces the keyboard focus ring, since the input itself is transparent.\n -->\n <div [ngClass]=\"iconBoxClasses\">\n <svg [size]=\"iconSize\" lucideCalendarDays aria-hidden=\"true\" class=\"pointer-events-none\"></svg>\n <ng-container [ngTemplateOutlet]=\"inputTemplate\"></ng-container>\n </div>\n } @else {\n <ng-container [ngTemplateOutlet]=\"inputTemplate\"></ng-container>\n }\n\n @if (showError) {\n @if (props.showAllErrors) {\n <div class=\"flex flex-col gap-y-1\">\n @for (error of errorMessages; track $index) {\n <mn-error-message [errorMessage]=\"error\" [id]=\"resolvedId + '-' + $index\"></mn-error-message>\n }\n </div>\n } @else {\n @if (errorMessage !== null) {\n <mn-error-message [errorMessage]=\"errorMessage\" [id]=\"resolvedId\"></mn-error-message>\n }\n }\n }\n</div>\n\n<!-- Single input definition, rendered either bare or inside the icon box above,\n so both variants share exactly the same bindings and behaviour. -->\n<ng-template #inputTemplate>\n <input\n #dateInput\n [id]=\"resolvedId\"\n [attr.name]=\"resolvedName\"\n [type]=\"resolvedMode\"\n [attr.placeholder]=\"uiConfig.placeholder || props.placeholder || null\"\n [attr.aria-label]=\"resolvedAriaLabel\"\n [attr.aria-invalid]=\"showError || null\"\n [attr.aria-describedby]=\"showError ? resolvedId + '-error' : null\"\n [disabled]=\"isDisabled\"\n [attr.min]=\"props.min || null\"\n [attr.max]=\"props.max || null\"\n [attr.step]=\"props.step || null\"\n [value]=\"value ?? ''\"\n [ngClass]=\"inputClasses\"\n (click)=\"handleClick(dateInput)\"\n (input)=\"handleInput(($any($event.target)).value)\"\n (blur)=\"handleBlur()\"\n />\n</ng-template>\n", styles: ["input::-webkit-calendar-picker-indicator{cursor:pointer}@media(pointer:coarse){input[type=date],input[type=datetime-local],input[type=time],input[type=month],input[type=week]{-webkit-appearance:none;appearance:none;min-width:0;box-sizing:border-box}}\n"] }]
|
|
2916
2998
|
}], ctorParameters: () => [], propDecorators: { props: [{
|
|
2917
2999
|
type: Input,
|
|
2918
3000
|
args: [{ required: true }]
|
|
@@ -8075,6 +8157,7 @@ const DEFAULT_CALENDAR_CONFIG = (() => {
|
|
|
8075
8157
|
nextLabel: 'Next',
|
|
8076
8158
|
upcomingEventsTitle: 'Upcoming events',
|
|
8077
8159
|
noUpcomingEvents: 'No upcoming events',
|
|
8160
|
+
moreEventsLabel: 'more',
|
|
8078
8161
|
viewLabels: { MONTH: 'Month', WEEK: 'Week', DAY: 'Day' },
|
|
8079
8162
|
shortDayNames: names.short,
|
|
8080
8163
|
longDayNames: names.long,
|
|
@@ -8239,17 +8322,21 @@ class CalendarMonthComponent {
|
|
|
8239
8322
|
/** Emits the date of a clicked day cell. */
|
|
8240
8323
|
dayClicked = new EventEmitter();
|
|
8241
8324
|
monthItems = [];
|
|
8242
|
-
|
|
8325
|
+
/** Short weekday column headers (e.g. "Mon"), kept compact for the narrow columns. */
|
|
8326
|
+
weekdayLabels;
|
|
8327
|
+
/** Word shown after the "+N" overflow count (e.g. "more"), from config. */
|
|
8328
|
+
moreEventsLabel = DEFAULT_CALENDAR_CONFIG.moreEventsLabel;
|
|
8243
8329
|
events = [];
|
|
8244
8330
|
destroy$ = new Subject();
|
|
8245
8331
|
formatter;
|
|
8246
8332
|
constructor() {
|
|
8247
8333
|
this.formatter = new DefaultCalendarDateFormatter();
|
|
8248
|
-
this.
|
|
8334
|
+
this.weekdayLabels = DEFAULT_CALENDAR_CONFIG.shortDayNames;
|
|
8249
8335
|
}
|
|
8250
8336
|
ngOnInit() {
|
|
8251
8337
|
const resolved = this.config ? resolveCalendarConfig(this.config) : { ...DEFAULT_CALENDAR_CONFIG };
|
|
8252
|
-
this.
|
|
8338
|
+
this.weekdayLabels = resolved.shortDayNames;
|
|
8339
|
+
this.moreEventsLabel = resolved.moreEventsLabel;
|
|
8253
8340
|
this.buildMonth();
|
|
8254
8341
|
if (this.eventsChanged) {
|
|
8255
8342
|
this.eventsChanged.pipe(takeUntil(this.destroy$)).subscribe(events => {
|
|
@@ -8325,11 +8412,11 @@ class CalendarMonthComponent {
|
|
|
8325
8412
|
};
|
|
8326
8413
|
}
|
|
8327
8414
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarMonthComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8328
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CalendarMonthComponent, isStandalone: true, selector: "mn-calendar-month", inputs: { focusDay: "focusDay", eventsChanged: "eventsChanged", focusDayChanged: "focusDayChanged", config: "config" }, outputs: { dayClicked: "dayClicked" }, ngImport: i0, template: "<div class=\"
|
|
8415
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CalendarMonthComponent, isStandalone: true, selector: "mn-calendar-month", inputs: { focusDay: "focusDay", eventsChanged: "eventsChanged", focusDayChanged: "focusDayChanged", config: "config" }, outputs: { dayClicked: "dayClicked" }, ngImport: i0, template: "<!-- Month grid. Cells are grouped by space rather than boxed by borders, in\n keeping with the date-selector-bar: no rules, soft rounded cells, a hover\n wash, and today marked by a tinted number alone (no circle). -->\n<!-- Rows are a compact fixed height rather than stretched to fill the container:\n a month with few events shouldn't leave each cell mostly empty. The panel\n scrolls if the grid is taller than the space it's given. -->\n<div class=\"w-full flex flex-col\" role=\"grid\" aria-label=\"Month view\">\n <div class=\"grid grid-cols-7 pb-2\">\n @for (day of weekdayLabels; track day) {\n <div class=\"pl-2 text-left text-[11px] font-semibold uppercase tracking-wide opacity-45\" role=\"columnheader\">{{ day }}</div>\n }\n </div>\n <div class=\"grid grid-cols-7 gap-1 auto-rows-[minmax(92px,auto)]\">\n @for (item of monthItems; track item.date.getTime()) {\n <div\n class=\"flex flex-col gap-1 min-h-0 overflow-hidden rounded-xl p-1.5 cursor-pointer transition-colors hover:bg-base-200 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-primary motion-reduce:transition-none\"\n [class.opacity-40]=\"!item.isCurrentMonth\"\n (keyup.enter)=\"onDayClick(item.date)\"\n (click)=\"onDayClick(item.date)\"\n tabindex=\"0\"\n role=\"gridcell\"\n [attr.aria-label]=\"item.date.toDateString()\">\n <span class=\"text-[13px] font-bold leading-none tabular-nums\" [class.text-primary]=\"item.isToday\">{{ item.dayNumber }}</span>\n <div class=\"flex flex-col gap-0.5 min-h-0\">\n @for (event of item.events.slice(0, 3); track $index) {\n <div\n class=\"flex items-center overflow-hidden rounded-md border-l-[3px] px-1.5 py-0.5 text-[11px] leading-tight\"\n [style.background-color]=\"event.color.secondaryColor\"\n [style.border-left-color]=\"event.color.primaryColor\"\n [style.color]=\"event.color.primaryColor\"\n [title]=\"event.title\">\n <span class=\"truncate font-semibold\">{{ event.title }}</span>\n </div>\n }\n <!-- Overflow indicator. Styled as a pill that lights up on hover so it\n reads as actionable; the whole cell already navigates to the Day\n view for this date on click / Enter, where every event is listed. -->\n @if (item.events.length > 3) {\n <span class=\"mt-0.5 inline-flex w-fit items-center rounded-md px-1.5 py-0.5 text-[10.5px] font-semibold opacity-60 transition-colors hover:bg-base-300 hover:opacity-100 motion-reduce:transition-none\">\n +{{ item.events.length - 3 }} {{ moreEventsLabel }}\n </span>\n }\n </div>\n </div>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
8329
8416
|
}
|
|
8330
8417
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarMonthComponent, decorators: [{
|
|
8331
8418
|
type: Component,
|
|
8332
|
-
args: [{ selector: 'mn-calendar-month', standalone: true, imports: [CommonModule], template: "<div class=\"
|
|
8419
|
+
args: [{ selector: 'mn-calendar-month', standalone: true, imports: [CommonModule], template: "<!-- Month grid. Cells are grouped by space rather than boxed by borders, in\n keeping with the date-selector-bar: no rules, soft rounded cells, a hover\n wash, and today marked by a tinted number alone (no circle). -->\n<!-- Rows are a compact fixed height rather than stretched to fill the container:\n a month with few events shouldn't leave each cell mostly empty. The panel\n scrolls if the grid is taller than the space it's given. -->\n<div class=\"w-full flex flex-col\" role=\"grid\" aria-label=\"Month view\">\n <div class=\"grid grid-cols-7 pb-2\">\n @for (day of weekdayLabels; track day) {\n <div class=\"pl-2 text-left text-[11px] font-semibold uppercase tracking-wide opacity-45\" role=\"columnheader\">{{ day }}</div>\n }\n </div>\n <div class=\"grid grid-cols-7 gap-1 auto-rows-[minmax(92px,auto)]\">\n @for (item of monthItems; track item.date.getTime()) {\n <div\n class=\"flex flex-col gap-1 min-h-0 overflow-hidden rounded-xl p-1.5 cursor-pointer transition-colors hover:bg-base-200 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-primary motion-reduce:transition-none\"\n [class.opacity-40]=\"!item.isCurrentMonth\"\n (keyup.enter)=\"onDayClick(item.date)\"\n (click)=\"onDayClick(item.date)\"\n tabindex=\"0\"\n role=\"gridcell\"\n [attr.aria-label]=\"item.date.toDateString()\">\n <span class=\"text-[13px] font-bold leading-none tabular-nums\" [class.text-primary]=\"item.isToday\">{{ item.dayNumber }}</span>\n <div class=\"flex flex-col gap-0.5 min-h-0\">\n @for (event of item.events.slice(0, 3); track $index) {\n <div\n class=\"flex items-center overflow-hidden rounded-md border-l-[3px] px-1.5 py-0.5 text-[11px] leading-tight\"\n [style.background-color]=\"event.color.secondaryColor\"\n [style.border-left-color]=\"event.color.primaryColor\"\n [style.color]=\"event.color.primaryColor\"\n [title]=\"event.title\">\n <span class=\"truncate font-semibold\">{{ event.title }}</span>\n </div>\n }\n <!-- Overflow indicator. Styled as a pill that lights up on hover so it\n reads as actionable; the whole cell already navigates to the Day\n view for this date on click / Enter, where every event is listed. -->\n @if (item.events.length > 3) {\n <span class=\"mt-0.5 inline-flex w-fit items-center rounded-md px-1.5 py-0.5 text-[10.5px] font-semibold opacity-60 transition-colors hover:bg-base-300 hover:opacity-100 motion-reduce:transition-none\">\n +{{ item.events.length - 3 }} {{ moreEventsLabel }}\n </span>\n }\n </div>\n </div>\n }\n </div>\n</div>\n" }]
|
|
8333
8420
|
}], ctorParameters: () => [], propDecorators: { focusDay: [{
|
|
8334
8421
|
type: Input
|
|
8335
8422
|
}], eventsChanged: [{
|
|
@@ -8547,11 +8634,11 @@ class CalendarEventDefaultComponent {
|
|
|
8547
8634
|
}
|
|
8548
8635
|
}
|
|
8549
8636
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarEventDefaultComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8550
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CalendarEventDefaultComponent, isStandalone: true, selector: "mn-calendar-event-default", ngImport: i0, template: "<div class=\"
|
|
8637
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CalendarEventDefaultComponent, isStandalone: true, selector: "mn-calendar-event-default", ngImport: i0, template: "<!-- The shared event chip: a colored spine and a tinted fill from the event's own\n colours, so it reads the same in week, day and month. Text inherits the\n event's primary colour, keeping it legible on either theme. -->\n<div class=\"h-full overflow-hidden cursor-pointer rounded-lg border-l-[3px] px-2 py-1 text-xs\"\n [style.background-color]=\"event.color.secondaryColor\"\n [style.border-left-color]=\"event.color.primaryColor\"\n [style.color]=\"event.color.primaryColor\">\n <div class=\"truncate font-semibold\">{{ event.title }}</div>\n <div class=\"truncate text-[11px] opacity-85\">{{ formattedTime }}</div>\n @if (event.description) {\n <div class=\"truncate text-[11px] opacity-75\">{{ event.description }}</div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
8551
8638
|
}
|
|
8552
8639
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarEventDefaultComponent, decorators: [{
|
|
8553
8640
|
type: Component,
|
|
8554
|
-
args: [{ selector: 'mn-calendar-event-default', standalone: true, imports: [CommonModule], template: "<div class=\"
|
|
8641
|
+
args: [{ selector: 'mn-calendar-event-default', standalone: true, imports: [CommonModule], template: "<!-- The shared event chip: a colored spine and a tinted fill from the event's own\n colours, so it reads the same in week, day and month. Text inherits the\n event's primary colour, keeping it legible on either theme. -->\n<div class=\"h-full overflow-hidden cursor-pointer rounded-lg border-l-[3px] px-2 py-1 text-xs\"\n [style.background-color]=\"event.color.secondaryColor\"\n [style.border-left-color]=\"event.color.primaryColor\"\n [style.color]=\"event.color.primaryColor\">\n <div class=\"truncate font-semibold\">{{ event.title }}</div>\n <div class=\"truncate text-[11px] opacity-85\">{{ formattedTime }}</div>\n @if (event.description) {\n <div class=\"truncate text-[11px] opacity-75\">{{ event.description }}</div>\n }\n</div>\n" }]
|
|
8555
8642
|
}], ctorParameters: () => [] });
|
|
8556
8643
|
|
|
8557
8644
|
/**
|
|
@@ -8596,11 +8683,11 @@ class CalendarEventComponent {
|
|
|
8596
8683
|
this.rendered = true;
|
|
8597
8684
|
}
|
|
8598
8685
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarEventComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8599
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: CalendarEventComponent, isStandalone: true, selector: "mn-calendar-event", inputs: { event: "event", customComponent: "customComponent" }, outputs: { eventClicked: "eventClicked" }, viewQueries: [{ propertyName: "eventContainer", first: true, predicate: ["eventContainer"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: "
|
|
8686
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: CalendarEventComponent, isStandalone: true, selector: "mn-calendar-event", inputs: { event: "event", customComponent: "customComponent" }, outputs: { eventClicked: "eventClicked" }, viewQueries: [{ propertyName: "eventContainer", first: true, predicate: ["eventContainer"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Host for the dynamically-rendered event component. Click/keyboard handling and\n the focus ring live on the week/day event wrapper, so this stays presentational\n to avoid a second, redundant tab stop per event. -->\n<div class=\"h-full w-full\">\n <ng-template #eventContainer></ng-template>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
8600
8687
|
}
|
|
8601
8688
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarEventComponent, decorators: [{
|
|
8602
8689
|
type: Component,
|
|
8603
|
-
args: [{ selector: 'mn-calendar-event', standalone: true, imports: [CommonModule], template: "
|
|
8690
|
+
args: [{ selector: 'mn-calendar-event', standalone: true, imports: [CommonModule], template: "<!-- Host for the dynamically-rendered event component. Click/keyboard handling and\n the focus ring live on the week/day event wrapper, so this stays presentational\n to avoid a second, redundant tab stop per event. -->\n<div class=\"h-full w-full\">\n <ng-template #eventContainer></ng-template>\n</div>\n" }]
|
|
8604
8691
|
}], propDecorators: { event: [{
|
|
8605
8692
|
type: Input
|
|
8606
8693
|
}], customComponent: [{
|
|
@@ -8639,6 +8726,8 @@ class CalendarWeekComponent {
|
|
|
8639
8726
|
totalRows = 0;
|
|
8640
8727
|
currentTimeRow = 0;
|
|
8641
8728
|
currentTimeCol = '';
|
|
8729
|
+
/** The current time, formatted for the label riding the now-line. */
|
|
8730
|
+
currentTimeLabel = '';
|
|
8642
8731
|
gridTemplateColumns = 'repeat(7, 1fr)';
|
|
8643
8732
|
dayColumnMap = [];
|
|
8644
8733
|
events = [];
|
|
@@ -8807,18 +8896,24 @@ class CalendarWeekComponent {
|
|
|
8807
8896
|
const dayInfo = this.dayColumnMap[dayIdx];
|
|
8808
8897
|
this.currentTimeCol = `${dayInfo.startCol} / span ${dayInfo.subColumns}`;
|
|
8809
8898
|
this.currentTimeRow = CalendarUtility.getCorrectRow(now.getHours(), now.getMinutes(), this.resolvedConfig.startHour);
|
|
8899
|
+
// formatTime is async; refresh the label and re-render when it resolves.
|
|
8900
|
+
this.formatter.formatTime(now).then(label => {
|
|
8901
|
+
this.currentTimeLabel = label;
|
|
8902
|
+
this.cdr.markForCheck();
|
|
8903
|
+
});
|
|
8810
8904
|
}
|
|
8811
8905
|
else {
|
|
8812
8906
|
this.currentTimeCol = '';
|
|
8813
8907
|
this.currentTimeRow = 0;
|
|
8908
|
+
this.currentTimeLabel = '';
|
|
8814
8909
|
}
|
|
8815
8910
|
}
|
|
8816
8911
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarWeekComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8817
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CalendarWeekComponent, isStandalone: true, selector: "mn-calendar-week", inputs: { focusDay: "focusDay", eventsChanged: "eventsChanged", focusDayChanged: "focusDayChanged", config: "config", calendarEventComponent: "calendarEventComponent" }, outputs: { eventClicked: "eventClicked" }, providers: [CalendarEventLayoutService], ngImport: i0, template: "<div class=\"
|
|
8912
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CalendarWeekComponent, isStandalone: true, selector: "mn-calendar-week", inputs: { focusDay: "focusDay", eventsChanged: "eventsChanged", focusDayChanged: "focusDayChanged", config: "config", calendarEventComponent: "calendarEventComponent" }, outputs: { eventClicked: "eventClicked" }, providers: [CalendarEventLayoutService], ngImport: i0, template: "<!-- Week grid. Hour rules are quiet hairlines; today's column header is a tinted\n number (no fill); the current time is a line carrying a small time bubble.\n Dynamic row/column placement stays inline \u2014 the layout maths lives in TS. -->\n<div class=\"w-full h-full flex flex-col overflow-hidden\" role=\"grid\" aria-label=\"Week view\">\n <div class=\"grid\" [style.grid-template-columns]=\"'60px ' + gridTemplateColumns\">\n <div></div>\n @for (col of columns; track col.dayName; let i = $index) {\n <div class=\"py-2 px-1 text-center\"\n [style.grid-column]=\"getHeaderColumn(i)\"\n role=\"columnheader\">\n <span class=\"block text-[11px] font-semibold uppercase tracking-wide opacity-50\">{{ col.dayName }}</span>\n <span class=\"text-lg font-bold tabular-nums\" [class.text-primary]=\"col.isToday\">{{ col.dayNumber }}</span>\n </div>\n }\n </div>\n <div class=\"grid grid-cols-[60px_1fr] flex-1 min-h-0 overflow-hidden items-stretch\">\n <div class=\"grid h-full min-h-0\" [style.grid-template-rows]=\"'repeat(' + totalRows + ', minmax(1.5rem, 1fr))'\">\n @for (row of hourRows; track row.topRow) {\n <div class=\"flex items-start justify-end min-h-0 overflow-hidden pr-2 text-[11px] tabular-nums opacity-50\"\n [style.grid-row]=\"row.topRow + '/' + row.bottomRow\">\n {{ row.hourLabel }}\n </div>\n }\n </div>\n <div class=\"grid relative auto-rows-fr h-full min-h-0\"\n [style.grid-template-rows]=\"'repeat(' + totalRows + ', minmax(1.5rem, 1fr))'\"\n [style.grid-template-columns]=\"gridTemplateColumns\">\n @for (row of hourRows; track row.topRow) {\n <div class=\"border-t border-base-200 pointer-events-none min-h-0\"\n [style.grid-row]=\"row.topRow + '/' + row.bottomRow\"\n [style.grid-column]=\"'1 / -1'\">\n </div>\n }\n @if (currentTimeRow > 0 && currentTimeCol) {\n <div class=\"relative z-[2] pointer-events-none\"\n [style.grid-row]=\"currentTimeRow\"\n [style.grid-column]=\"currentTimeCol\">\n <div class=\"absolute -left-1 -top-1 h-2 w-2 rounded-full bg-error\"></div>\n <div class=\"h-0.5 w-full bg-error\"></div>\n <div class=\"absolute left-0 top-0 -translate-x-2 -translate-y-1/2 rounded-full bg-error px-1.5 py-0.5 text-[10px] font-bold tabular-nums text-error-content shadow\">{{ currentTimeLabel }}</div>\n </div>\n }\n @for (event of displayEvents; track $index) {\n <div class=\"z-[1] min-h-0 overflow-hidden rounded-lg p-0.5 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary\"\n [style.grid-row]=\"getEventRow(event)\"\n [style.grid-column]=\"getEventColumn(event)\"\n (click)=\"onEventClick(event)\"\n (keyup.enter)=\"onEventClick(event)\"\n tabindex=\"0\">\n <mn-calendar-event [customComponent]=\"calendarEventComponent\" [event]=\"event\"></mn-calendar-event>\n </div>\n }\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CalendarEventComponent, selector: "mn-calendar-event", inputs: ["event", "customComponent"], outputs: ["eventClicked"] }] });
|
|
8818
8913
|
}
|
|
8819
8914
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarWeekComponent, decorators: [{
|
|
8820
8915
|
type: Component,
|
|
8821
|
-
args: [{ selector: 'mn-calendar-week', standalone: true, imports: [CommonModule, CalendarEventComponent], providers: [CalendarEventLayoutService], template: "<div class=\"
|
|
8916
|
+
args: [{ selector: 'mn-calendar-week', standalone: true, imports: [CommonModule, CalendarEventComponent], providers: [CalendarEventLayoutService], template: "<!-- Week grid. Hour rules are quiet hairlines; today's column header is a tinted\n number (no fill); the current time is a line carrying a small time bubble.\n Dynamic row/column placement stays inline \u2014 the layout maths lives in TS. -->\n<div class=\"w-full h-full flex flex-col overflow-hidden\" role=\"grid\" aria-label=\"Week view\">\n <div class=\"grid\" [style.grid-template-columns]=\"'60px ' + gridTemplateColumns\">\n <div></div>\n @for (col of columns; track col.dayName; let i = $index) {\n <div class=\"py-2 px-1 text-center\"\n [style.grid-column]=\"getHeaderColumn(i)\"\n role=\"columnheader\">\n <span class=\"block text-[11px] font-semibold uppercase tracking-wide opacity-50\">{{ col.dayName }}</span>\n <span class=\"text-lg font-bold tabular-nums\" [class.text-primary]=\"col.isToday\">{{ col.dayNumber }}</span>\n </div>\n }\n </div>\n <div class=\"grid grid-cols-[60px_1fr] flex-1 min-h-0 overflow-hidden items-stretch\">\n <div class=\"grid h-full min-h-0\" [style.grid-template-rows]=\"'repeat(' + totalRows + ', minmax(1.5rem, 1fr))'\">\n @for (row of hourRows; track row.topRow) {\n <div class=\"flex items-start justify-end min-h-0 overflow-hidden pr-2 text-[11px] tabular-nums opacity-50\"\n [style.grid-row]=\"row.topRow + '/' + row.bottomRow\">\n {{ row.hourLabel }}\n </div>\n }\n </div>\n <div class=\"grid relative auto-rows-fr h-full min-h-0\"\n [style.grid-template-rows]=\"'repeat(' + totalRows + ', minmax(1.5rem, 1fr))'\"\n [style.grid-template-columns]=\"gridTemplateColumns\">\n @for (row of hourRows; track row.topRow) {\n <div class=\"border-t border-base-200 pointer-events-none min-h-0\"\n [style.grid-row]=\"row.topRow + '/' + row.bottomRow\"\n [style.grid-column]=\"'1 / -1'\">\n </div>\n }\n @if (currentTimeRow > 0 && currentTimeCol) {\n <div class=\"relative z-[2] pointer-events-none\"\n [style.grid-row]=\"currentTimeRow\"\n [style.grid-column]=\"currentTimeCol\">\n <div class=\"absolute -left-1 -top-1 h-2 w-2 rounded-full bg-error\"></div>\n <div class=\"h-0.5 w-full bg-error\"></div>\n <div class=\"absolute left-0 top-0 -translate-x-2 -translate-y-1/2 rounded-full bg-error px-1.5 py-0.5 text-[10px] font-bold tabular-nums text-error-content shadow\">{{ currentTimeLabel }}</div>\n </div>\n }\n @for (event of displayEvents; track $index) {\n <div class=\"z-[1] min-h-0 overflow-hidden rounded-lg p-0.5 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary\"\n [style.grid-row]=\"getEventRow(event)\"\n [style.grid-column]=\"getEventColumn(event)\"\n (click)=\"onEventClick(event)\"\n (keyup.enter)=\"onEventClick(event)\"\n tabindex=\"0\">\n <mn-calendar-event [customComponent]=\"calendarEventComponent\" [event]=\"event\"></mn-calendar-event>\n </div>\n }\n </div>\n </div>\n</div>\n" }]
|
|
8822
8917
|
}], ctorParameters: () => [], propDecorators: { focusDay: [{
|
|
8823
8918
|
type: Input
|
|
8824
8919
|
}], eventsChanged: [{
|
|
@@ -8859,6 +8954,8 @@ class CalendarDayComponent {
|
|
|
8859
8954
|
totalRows = 0;
|
|
8860
8955
|
totalColumns = 1;
|
|
8861
8956
|
currentTimeRow = 0;
|
|
8957
|
+
/** The current time, formatted for the label riding the now-line. */
|
|
8958
|
+
currentTimeLabel = '';
|
|
8862
8959
|
isToday = false;
|
|
8863
8960
|
dayName = '';
|
|
8864
8961
|
events = [];
|
|
@@ -8962,8 +9059,7 @@ class CalendarDayComponent {
|
|
|
8962
9059
|
this.displayEvents = this.layoutService.calculateMultiDayEvents(filtered, this.resolvedConfig.startHour, this.resolvedConfig.endHour, rangeStart, rangeEnd);
|
|
8963
9060
|
this.layoutService.assignColumnsToEvents(this.displayEvents);
|
|
8964
9061
|
this.layoutService.assignWidthsToEvents(this.displayEvents, rangeStart, rangeEnd);
|
|
8965
|
-
|
|
8966
|
-
this.totalColumns = maxCol;
|
|
9062
|
+
this.totalColumns = this.displayEvents.reduce((max, e) => Math.max(max, (e.column ?? 0) + (e.width ?? 1)), 1);
|
|
8967
9063
|
}
|
|
8968
9064
|
/** Updates the current-time red line position. */
|
|
8969
9065
|
updateCurrentTime() {
|
|
@@ -8971,18 +9067,24 @@ class CalendarDayComponent {
|
|
|
8971
9067
|
if (this.focusDay && this.formatter.isSameDay(this.focusDay, now)) {
|
|
8972
9068
|
this.currentTimeRow = CalendarUtility.getCorrectRow(now.getHours(), now.getMinutes(), this.resolvedConfig.startHour);
|
|
8973
9069
|
this.isToday = true;
|
|
9070
|
+
// formatTime is async; refresh the label and re-render when it resolves.
|
|
9071
|
+
this.formatter.formatTime(now).then(label => {
|
|
9072
|
+
this.currentTimeLabel = label;
|
|
9073
|
+
this.cdr.markForCheck();
|
|
9074
|
+
});
|
|
8974
9075
|
}
|
|
8975
9076
|
else {
|
|
8976
9077
|
this.currentTimeRow = 0;
|
|
8977
9078
|
this.isToday = false;
|
|
9079
|
+
this.currentTimeLabel = '';
|
|
8978
9080
|
}
|
|
8979
9081
|
}
|
|
8980
9082
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarDayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8981
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CalendarDayComponent, isStandalone: true, selector: "mn-calendar-day", inputs: { focusDay: "focusDay", eventsChanged: "eventsChanged", focusDayChanged: "focusDayChanged", config: "config", calendarEventComponent: "calendarEventComponent" }, outputs: { eventClicked: "eventClicked" }, providers: [CalendarEventLayoutService], ngImport: i0, template: "<div class=\"
|
|
9083
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CalendarDayComponent, isStandalone: true, selector: "mn-calendar-day", inputs: { focusDay: "focusDay", eventsChanged: "eventsChanged", focusDayChanged: "focusDayChanged", config: "config", calendarEventComponent: "calendarEventComponent" }, outputs: { eventClicked: "eventClicked" }, providers: [CalendarEventLayoutService], ngImport: i0, template: "<!-- Day grid. One column, its header centered over that column (the toolbar\n already names the date). Today's number is tinted, not filled; the now-line\n carries a time bubble that overhangs into the gutter. -->\n<div class=\"w-full h-full flex flex-col overflow-hidden\" role=\"grid\" aria-label=\"Day view\">\n <div class=\"grid grid-cols-[60px_1fr]\">\n <div></div>\n <div class=\"flex items-center justify-center gap-2 py-2 px-1\" role=\"columnheader\">\n <span class=\"text-[11px] font-semibold uppercase tracking-wide opacity-50\">{{ dayName }}</span>\n <span class=\"text-base font-bold tabular-nums\" [class.text-primary]=\"isToday\">{{ focusDay.getDate() }}</span>\n </div>\n </div>\n <div class=\"grid grid-cols-[60px_1fr] flex-1 min-h-0 overflow-hidden items-stretch\">\n <div class=\"grid h-full min-h-0\" [style.grid-template-rows]=\"'repeat(' + totalRows + ', minmax(1.5rem, 1fr))'\">\n @for (row of hourRows; track row.topRow) {\n <div class=\"flex items-start justify-end min-h-0 overflow-hidden pr-2 text-[11px] tabular-nums opacity-50\"\n [style.grid-row]=\"row.topRow + '/' + row.bottomRow\">\n {{ row.hourLabel }}\n </div>\n }\n </div>\n <div class=\"grid relative auto-rows-fr h-full min-h-0\"\n [style.grid-template-rows]=\"'repeat(' + totalRows + ', minmax(1.5rem, 1fr))'\"\n [style.grid-template-columns]=\"'repeat(' + totalColumns + ', 1fr)'\">\n @for (row of hourRows; track row.topRow) {\n <div class=\"border-t border-base-200 pointer-events-none min-h-0\"\n [style.grid-row]=\"row.topRow + '/' + row.bottomRow\"\n [style.grid-column]=\"'1 / -1'\">\n </div>\n }\n @if (currentTimeRow > 0 && isToday) {\n <div class=\"relative z-[2] pointer-events-none\"\n [style.grid-row]=\"currentTimeRow\"\n [style.grid-column]=\"'1 / -1'\">\n <div class=\"absolute -left-1 -top-1 h-2 w-2 rounded-full bg-error\"></div>\n <div class=\"h-0.5 w-full bg-error\"></div>\n <div class=\"absolute left-0 top-0 -translate-x-2 -translate-y-1/2 rounded-full bg-error px-1.5 py-0.5 text-[10px] font-bold tabular-nums text-error-content shadow\">{{ currentTimeLabel }}</div>\n </div>\n }\n @for (event of displayEvents; track $index) {\n <div class=\"z-[1] min-h-0 overflow-hidden rounded-lg p-0.5 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary\"\n [style.grid-row]=\"getEventRow(event)\"\n [style.grid-column]=\"getEventColumn(event)\"\n (click)=\"onEventClick(event)\"\n (keyup.enter)=\"onEventClick(event)\"\n tabindex=\"0\">\n <mn-calendar-event [customComponent]=\"calendarEventComponent\" [event]=\"event\"></mn-calendar-event>\n </div>\n }\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CalendarEventComponent, selector: "mn-calendar-event", inputs: ["event", "customComponent"], outputs: ["eventClicked"] }] });
|
|
8982
9084
|
}
|
|
8983
9085
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarDayComponent, decorators: [{
|
|
8984
9086
|
type: Component,
|
|
8985
|
-
args: [{ selector: 'mn-calendar-day', standalone: true, imports: [CommonModule, CalendarEventComponent], providers: [CalendarEventLayoutService], template: "<div class=\"
|
|
9087
|
+
args: [{ selector: 'mn-calendar-day', standalone: true, imports: [CommonModule, CalendarEventComponent], providers: [CalendarEventLayoutService], template: "<!-- Day grid. One column, its header centered over that column (the toolbar\n already names the date). Today's number is tinted, not filled; the now-line\n carries a time bubble that overhangs into the gutter. -->\n<div class=\"w-full h-full flex flex-col overflow-hidden\" role=\"grid\" aria-label=\"Day view\">\n <div class=\"grid grid-cols-[60px_1fr]\">\n <div></div>\n <div class=\"flex items-center justify-center gap-2 py-2 px-1\" role=\"columnheader\">\n <span class=\"text-[11px] font-semibold uppercase tracking-wide opacity-50\">{{ dayName }}</span>\n <span class=\"text-base font-bold tabular-nums\" [class.text-primary]=\"isToday\">{{ focusDay.getDate() }}</span>\n </div>\n </div>\n <div class=\"grid grid-cols-[60px_1fr] flex-1 min-h-0 overflow-hidden items-stretch\">\n <div class=\"grid h-full min-h-0\" [style.grid-template-rows]=\"'repeat(' + totalRows + ', minmax(1.5rem, 1fr))'\">\n @for (row of hourRows; track row.topRow) {\n <div class=\"flex items-start justify-end min-h-0 overflow-hidden pr-2 text-[11px] tabular-nums opacity-50\"\n [style.grid-row]=\"row.topRow + '/' + row.bottomRow\">\n {{ row.hourLabel }}\n </div>\n }\n </div>\n <div class=\"grid relative auto-rows-fr h-full min-h-0\"\n [style.grid-template-rows]=\"'repeat(' + totalRows + ', minmax(1.5rem, 1fr))'\"\n [style.grid-template-columns]=\"'repeat(' + totalColumns + ', 1fr)'\">\n @for (row of hourRows; track row.topRow) {\n <div class=\"border-t border-base-200 pointer-events-none min-h-0\"\n [style.grid-row]=\"row.topRow + '/' + row.bottomRow\"\n [style.grid-column]=\"'1 / -1'\">\n </div>\n }\n @if (currentTimeRow > 0 && isToday) {\n <div class=\"relative z-[2] pointer-events-none\"\n [style.grid-row]=\"currentTimeRow\"\n [style.grid-column]=\"'1 / -1'\">\n <div class=\"absolute -left-1 -top-1 h-2 w-2 rounded-full bg-error\"></div>\n <div class=\"h-0.5 w-full bg-error\"></div>\n <div class=\"absolute left-0 top-0 -translate-x-2 -translate-y-1/2 rounded-full bg-error px-1.5 py-0.5 text-[10px] font-bold tabular-nums text-error-content shadow\">{{ currentTimeLabel }}</div>\n </div>\n }\n @for (event of displayEvents; track $index) {\n <div class=\"z-[1] min-h-0 overflow-hidden rounded-lg p-0.5 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary\"\n [style.grid-row]=\"getEventRow(event)\"\n [style.grid-column]=\"getEventColumn(event)\"\n (click)=\"onEventClick(event)\"\n (keyup.enter)=\"onEventClick(event)\"\n tabindex=\"0\">\n <mn-calendar-event [customComponent]=\"calendarEventComponent\" [event]=\"event\"></mn-calendar-event>\n </div>\n }\n </div>\n </div>\n</div>\n" }]
|
|
8986
9088
|
}], ctorParameters: () => [], propDecorators: { focusDay: [{
|
|
8987
9089
|
type: Input
|
|
8988
9090
|
}], eventsChanged: [{
|
|
@@ -9020,11 +9122,11 @@ class UpcomingEventRowComponent {
|
|
|
9020
9122
|
}
|
|
9021
9123
|
}
|
|
9022
9124
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UpcomingEventRowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9023
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UpcomingEventRowComponent, isStandalone: true, selector: "mn-upcoming-event-row", inputs: { event: "event" }, outputs: { eventClicked: "eventClicked" }, ngImport: i0, template: "<div (click)=\"eventClicked.emit(event)\"
|
|
9125
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UpcomingEventRowComponent, isStandalone: true, selector: "mn-upcoming-event-row", inputs: { event: "event" }, outputs: { eventClicked: "eventClicked" }, ngImport: i0, template: "<!-- Sidebar row in the shared chip language: a colored rail from the event's own\n colour, a hover wash, and space-grouped content \u2014 no boxed borders. -->\n<div (click)=\"eventClicked.emit(event)\"\n (keyup.enter)=\"eventClicked.emit(event)\"\n class=\"grid grid-cols-[3px_1fr] items-stretch gap-2.5 mb-1.5 cursor-pointer rounded-lg p-2 transition-colors hover:bg-base-200 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-primary motion-reduce:transition-none\"\n tabindex=\"0\">\n <div class=\"rounded-full\" [style.background-color]=\"event.color.primaryColor\"></div>\n <div class=\"min-w-0\">\n <div class=\"truncate text-[13px] font-semibold\">{{ event.title }}</div>\n <div class=\"text-xs tabular-nums opacity-60\">{{ formattedDate }}</div>\n @if (event.description) {\n <div class=\"truncate text-xs opacity-50\">{{ event.description }}</div>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
9024
9126
|
}
|
|
9025
9127
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UpcomingEventRowComponent, decorators: [{
|
|
9026
9128
|
type: Component,
|
|
9027
|
-
args: [{ selector: 'mn-upcoming-event-row', standalone: true, imports: [CommonModule], template: "<div (click)=\"eventClicked.emit(event)\"
|
|
9129
|
+
args: [{ selector: 'mn-upcoming-event-row', standalone: true, imports: [CommonModule], template: "<!-- Sidebar row in the shared chip language: a colored rail from the event's own\n colour, a hover wash, and space-grouped content \u2014 no boxed borders. -->\n<div (click)=\"eventClicked.emit(event)\"\n (keyup.enter)=\"eventClicked.emit(event)\"\n class=\"grid grid-cols-[3px_1fr] items-stretch gap-2.5 mb-1.5 cursor-pointer rounded-lg p-2 transition-colors hover:bg-base-200 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-primary motion-reduce:transition-none\"\n tabindex=\"0\">\n <div class=\"rounded-full\" [style.background-color]=\"event.color.primaryColor\"></div>\n <div class=\"min-w-0\">\n <div class=\"truncate text-[13px] font-semibold\">{{ event.title }}</div>\n <div class=\"text-xs tabular-nums opacity-60\">{{ formattedDate }}</div>\n @if (event.description) {\n <div class=\"truncate text-xs opacity-50\">{{ event.description }}</div>\n }\n </div>\n</div>\n" }]
|
|
9028
9130
|
}], ctorParameters: () => [], propDecorators: { event: [{
|
|
9029
9131
|
type: Input
|
|
9030
9132
|
}], eventClicked: [{
|
|
@@ -9081,11 +9183,11 @@ class UpcomingEventsComponent {
|
|
|
9081
9183
|
return event.id;
|
|
9082
9184
|
}
|
|
9083
9185
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UpcomingEventsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9084
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UpcomingEventsComponent, isStandalone: true, selector: "mn-upcoming-events", inputs: { eventsChanged: "eventsChanged", config: "config" }, outputs: { eventClicked: "eventClicked" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"
|
|
9186
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: UpcomingEventsComponent, isStandalone: true, selector: "mn-upcoming-events", inputs: { eventsChanged: "eventsChanged", config: "config" }, outputs: { eventClicked: "eventClicked" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"p-4\" role=\"complementary\" aria-label=\"Upcoming events\">\n <div class=\"mb-3 flex items-center gap-2\">\n <h3 class=\"text-sm font-bold tracking-wide\">{{ title }}</h3>\n @if (upcomingEvents.length) {\n <span class=\"text-xs font-semibold tabular-nums opacity-45\">{{ upcomingEvents.length }}</span>\n }\n </div>\n @for (event of upcomingEvents; track $index) {\n <mn-upcoming-event-row\n [event]=\"event\"\n (eventClicked)=\"eventClicked.emit($event)\">\n </mn-upcoming-event-row>\n }\n @if (upcomingEvents.length === 0) {\n <div class=\"text-sm opacity-50\">{{ noEventsMessage }}</div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: UpcomingEventRowComponent, selector: "mn-upcoming-event-row", inputs: ["event"], outputs: ["eventClicked"] }] });
|
|
9085
9187
|
}
|
|
9086
9188
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UpcomingEventsComponent, decorators: [{
|
|
9087
9189
|
type: Component,
|
|
9088
|
-
args: [{ selector: 'mn-upcoming-events', standalone: true, imports: [CommonModule, UpcomingEventRowComponent], template: "<div class=\"
|
|
9190
|
+
args: [{ selector: 'mn-upcoming-events', standalone: true, imports: [CommonModule, UpcomingEventRowComponent], template: "<div class=\"p-4\" role=\"complementary\" aria-label=\"Upcoming events\">\n <div class=\"mb-3 flex items-center gap-2\">\n <h3 class=\"text-sm font-bold tracking-wide\">{{ title }}</h3>\n @if (upcomingEvents.length) {\n <span class=\"text-xs font-semibold tabular-nums opacity-45\">{{ upcomingEvents.length }}</span>\n }\n </div>\n @for (event of upcomingEvents; track $index) {\n <mn-upcoming-event-row\n [event]=\"event\"\n (eventClicked)=\"eventClicked.emit($event)\">\n </mn-upcoming-event-row>\n }\n @if (upcomingEvents.length === 0) {\n <div class=\"text-sm opacity-50\">{{ noEventsMessage }}</div>\n }\n</div>\n" }]
|
|
9089
9191
|
}], ctorParameters: () => [], propDecorators: { eventsChanged: [{
|
|
9090
9192
|
type: Input
|
|
9091
9193
|
}], config: [{
|
|
@@ -9296,8 +9398,12 @@ class CalendarViewComponent {
|
|
|
9296
9398
|
checkMobileView() {
|
|
9297
9399
|
const wasMobile = this.isMobileView;
|
|
9298
9400
|
const width = window.innerWidth;
|
|
9299
|
-
|
|
9300
|
-
|
|
9401
|
+
// Honour the configured mobile breakpoint (default 768) rather than a hard-coded
|
|
9402
|
+
// width: a seven-column week is cramped well before a phone's width, so the day
|
|
9403
|
+
// view takes over earlier — and consumers can tune where via `mobileBreakpoint`.
|
|
9404
|
+
const breakpoint = this.config.mobileBreakpoint;
|
|
9405
|
+
this.isMobileView = width < breakpoint;
|
|
9406
|
+
this.isTabletView = width >= breakpoint && width < 1024;
|
|
9301
9407
|
if (this.isMobileView && !wasMobile) {
|
|
9302
9408
|
this.currentView = CalendarView.DAY;
|
|
9303
9409
|
}
|
|
@@ -9330,7 +9436,7 @@ class CalendarViewComponent {
|
|
|
9330
9436
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9331
9437
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: CalendarViewComponent, isStandalone: true, selector: "mn-calendar-view", inputs: { showButton: "showButton", buttonTitle: "buttonTitle", buttons: "buttons", CalendarEventComponent: "CalendarEventComponent", NewCalendarItemsEvent: "NewCalendarItemsEvent" }, outputs: { RequestNewCalendarItemsEvent: "RequestNewCalendarItemsEvent", CalendarItemClickedEvent: "CalendarItemClickedEvent", ButtonClickedEvent: "ButtonClickedEvent" }, host: { listeners: { "window:resize": "onResize()" } }, providers: [
|
|
9332
9438
|
provideMnCalendarConfig(DEFAULT_CALENDAR_CONFIG),
|
|
9333
|
-
], ngImport: i0, template: "<div class=\"w-full h-full flex flex-col\" role=\"application\" aria-label=\"Calendar\">\n\n <!--
|
|
9439
|
+
], ngImport: i0, template: "<div class=\"w-full h-full flex flex-col\" role=\"application\" aria-label=\"Calendar\">\n\n <!-- Toolbar spans the full width so the primary action can sit above the\n upcoming-events sidebar, using that otherwise-empty space. On mobile it\n reflows: Today and the controls share the top row, and the \u2039 date \u203A nav\n drops to its own full-width row below. Responsive `order` does the reflow. -->\n <div class=\"flex flex-wrap items-center gap-x-3 gap-y-2 py-3\">\n\n <button (click)=\"goToToday()\"\n [data]=\"{ variant: 'outline', size: 'md', color: 'primary' }\"\n class=\"order-1 shrink-0\"\n mnButton\n type=\"button\">\n {{ config.todayLabel }}\n </button>\n\n <!-- Jumping somewhere far off, which the arrows would take all day to reach.\n On mobile the picker collapses to its icon-only variant. Its `ml-auto`\n pulls the picker \u2014 and the switcher and action button that follow it \u2014 to\n the right of the row on every width, leaving Today (and the nav) on the\n left, so the controls align right consistently across devices. -->\n <mn-lib-datetime (ngModelChange)=\"onPickDate($event)\"\n [ngModel]=\"focusDayString\"\n [props]=\"{ id: panelId + '-date', mode: 'date', placeholder: config.pickDateLabel, size: 'md', borderRadius: 'lg', hover: true, iconOnly: isMobileView }\"\n class=\"order-2 ml-auto block lg:order-3\"></mn-lib-datetime>\n\n <!-- View switcher (month / week / day) \u2014 hidden on mobile, which is forced to day view. -->\n @if (!isMobileView) {\n <div aria-label=\"Calendar view\" class=\"order-3 flex border border-base-300 rounded-md overflow-hidden lg:order-4\" role=\"tablist\">\n @for (view of viewOptions; track view.value) {\n <button\n (click)=\"switchView(view.value)\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-selected]=\"currentView === view.value\"\n [data]=\"{ size: 'md', variant: currentView === view.value ? 'fill' : 'text', color: 'primary' }\"\n mnButton\n role=\"tab\"\n type=\"button\">\n {{ view.label }}\n </button>\n }\n </div>\n }\n\n <!-- Action buttons (custom buttons + optional showButton CTA). They follow the\n picker, so the picker's `ml-auto` carries them to the right of the row on\n every width \u2014 on desktop that lands them above the upcoming-events sidebar. -->\n @if (buttons.length || showButton) {\n <div class=\"order-4 flex items-center gap-2 lg:order-5\">\n @for (btn of buttons; track btn.label) {\n <button (click)=\"btn.onClick()\" [data]=\"btn.buttonData || {}\" mnButton type=\"button\">\n {{ btn.label }}\n </button>\n }\n @if (showButton) {\n <button (click)=\"ButtonClickedEvent.emit()\" [data]=\"{}\" mnButton type=\"button\">\n {{ buttonTitle }}\n </button>\n }\n </div>\n }\n\n <!-- Stepping through time. The arrows sit either side of the date they move,\n so the control and the thing it controls read as one unit. On mobile this\n nav takes its own full-width row (basis-full, ordered last) with the date\n centred between the arrows at the row's edges; on desktop it sits inline\n after Today. `aria-live` announces the new period after each step. -->\n <div class=\"order-5 flex basis-full items-center justify-between gap-1 lg:order-2 lg:basis-auto lg:justify-start\">\n <button (click)=\"navigate(-1)\"\n [attr.aria-label]=\"config.previousLabel\"\n [data]=\"{ variant: 'text', size: 'md', color: 'gray' }\"\n class=\"shrink-0\"\n mnButton\n type=\"button\">\n <svg [size]=\"18\" lucideChevronLeft></svg>\n </button>\n\n <h2 aria-live=\"polite\" class=\"text-center text-base font-semibold whitespace-nowrap px-1 lg:min-w-56\">\n {{ periodLabel }}\n </h2>\n\n <button (click)=\"navigate(1)\"\n [attr.aria-label]=\"config.nextLabel\"\n [data]=\"{ variant: 'text', size: 'md', color: 'gray' }\"\n class=\"shrink-0\"\n mnButton\n type=\"button\">\n <svg [size]=\"18\" lucideChevronRight></svg>\n </button>\n </div>\n\n </div>\n\n <div class=\"grid grid-cols-1 lg:grid-cols-[1fr_220px] gap-3 flex-1 min-h-0\">\n <div [id]=\"panelId\" class=\"min-w-0 min-h-0 overflow-hidden overflow-y-auto\" role=\"tabpanel\">\n @if (currentView === CalendarView.MONTH) {\n <mn-calendar-month\n [focusDay]=\"focusDay\"\n [eventsChanged]=\"internalEventsChanged\"\n [focusDayChanged]=\"internalFocusDayChanged\"\n [config]=\"config\"\n (dayClicked)=\"onMonthDayClick($event)\">\n </mn-calendar-month>\n }\n @if (currentView === CalendarView.WEEK) {\n <mn-calendar-week\n [focusDay]=\"focusDay\"\n [eventsChanged]=\"internalEventsChanged\"\n [focusDayChanged]=\"internalFocusDayChanged\"\n [config]=\"config\"\n [calendarEventComponent]=\"CalendarEventComponent\"\n (eventClicked)=\"onEventClick($event)\">\n </mn-calendar-week>\n }\n @if (currentView === CalendarView.DAY) {\n <mn-calendar-day\n [focusDay]=\"focusDay\"\n [eventsChanged]=\"internalEventsChanged\"\n [focusDayChanged]=\"internalFocusDayChanged\"\n [config]=\"config\"\n [calendarEventComponent]=\"CalendarEventComponent\"\n (eventClicked)=\"onEventClick($event)\">\n </mn-calendar-day>\n }\n </div>\n <div class=\"hidden lg:block border-l border-base-300 overflow-auto\">\n <mn-upcoming-events\n [eventsChanged]=\"internalEventsChanged\"\n [config]=\"config\"\n (eventClicked)=\"onEventClick($event)\">\n </mn-upcoming-events>\n </div>\n </div>\n\n</div>\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: CalendarMonthComponent, selector: "mn-calendar-month", inputs: ["focusDay", "eventsChanged", "focusDayChanged", "config"], outputs: ["dayClicked"] }, { kind: "component", type: CalendarWeekComponent, selector: "mn-calendar-week", inputs: ["focusDay", "eventsChanged", "focusDayChanged", "config", "calendarEventComponent"], outputs: ["eventClicked"] }, { kind: "component", type: CalendarDayComponent, selector: "mn-calendar-day", inputs: ["focusDay", "eventsChanged", "focusDayChanged", "config", "calendarEventComponent"], outputs: ["eventClicked"] }, { kind: "component", type: UpcomingEventsComponent, selector: "mn-upcoming-events", inputs: ["eventsChanged", "config"], outputs: ["eventClicked"] }, { kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }, { kind: "component", type: MnDatetime, selector: "mn-lib-datetime", inputs: ["props"] }, { kind: "component", type: LucideChevronLeft, selector: "svg[lucideChevronLeft]" }, { kind: "component", type: LucideChevronRight, selector: "svg[lucideChevronRight]" }] });
|
|
9334
9440
|
}
|
|
9335
9441
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarViewComponent, decorators: [{
|
|
9336
9442
|
type: Component,
|
|
@@ -9347,7 +9453,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
9347
9453
|
LucideChevronRight
|
|
9348
9454
|
], providers: [
|
|
9349
9455
|
provideMnCalendarConfig(DEFAULT_CALENDAR_CONFIG),
|
|
9350
|
-
], template: "<div class=\"w-full h-full flex flex-col\" role=\"application\" aria-label=\"Calendar\">\n\n <!--
|
|
9456
|
+
], template: "<div class=\"w-full h-full flex flex-col\" role=\"application\" aria-label=\"Calendar\">\n\n <!-- Toolbar spans the full width so the primary action can sit above the\n upcoming-events sidebar, using that otherwise-empty space. On mobile it\n reflows: Today and the controls share the top row, and the \u2039 date \u203A nav\n drops to its own full-width row below. Responsive `order` does the reflow. -->\n <div class=\"flex flex-wrap items-center gap-x-3 gap-y-2 py-3\">\n\n <button (click)=\"goToToday()\"\n [data]=\"{ variant: 'outline', size: 'md', color: 'primary' }\"\n class=\"order-1 shrink-0\"\n mnButton\n type=\"button\">\n {{ config.todayLabel }}\n </button>\n\n <!-- Jumping somewhere far off, which the arrows would take all day to reach.\n On mobile the picker collapses to its icon-only variant. Its `ml-auto`\n pulls the picker \u2014 and the switcher and action button that follow it \u2014 to\n the right of the row on every width, leaving Today (and the nav) on the\n left, so the controls align right consistently across devices. -->\n <mn-lib-datetime (ngModelChange)=\"onPickDate($event)\"\n [ngModel]=\"focusDayString\"\n [props]=\"{ id: panelId + '-date', mode: 'date', placeholder: config.pickDateLabel, size: 'md', borderRadius: 'lg', hover: true, iconOnly: isMobileView }\"\n class=\"order-2 ml-auto block lg:order-3\"></mn-lib-datetime>\n\n <!-- View switcher (month / week / day) \u2014 hidden on mobile, which is forced to day view. -->\n @if (!isMobileView) {\n <div aria-label=\"Calendar view\" class=\"order-3 flex border border-base-300 rounded-md overflow-hidden lg:order-4\" role=\"tablist\">\n @for (view of viewOptions; track view.value) {\n <button\n (click)=\"switchView(view.value)\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-selected]=\"currentView === view.value\"\n [data]=\"{ size: 'md', variant: currentView === view.value ? 'fill' : 'text', color: 'primary' }\"\n mnButton\n role=\"tab\"\n type=\"button\">\n {{ view.label }}\n </button>\n }\n </div>\n }\n\n <!-- Action buttons (custom buttons + optional showButton CTA). They follow the\n picker, so the picker's `ml-auto` carries them to the right of the row on\n every width \u2014 on desktop that lands them above the upcoming-events sidebar. -->\n @if (buttons.length || showButton) {\n <div class=\"order-4 flex items-center gap-2 lg:order-5\">\n @for (btn of buttons; track btn.label) {\n <button (click)=\"btn.onClick()\" [data]=\"btn.buttonData || {}\" mnButton type=\"button\">\n {{ btn.label }}\n </button>\n }\n @if (showButton) {\n <button (click)=\"ButtonClickedEvent.emit()\" [data]=\"{}\" mnButton type=\"button\">\n {{ buttonTitle }}\n </button>\n }\n </div>\n }\n\n <!-- Stepping through time. The arrows sit either side of the date they move,\n so the control and the thing it controls read as one unit. On mobile this\n nav takes its own full-width row (basis-full, ordered last) with the date\n centred between the arrows at the row's edges; on desktop it sits inline\n after Today. `aria-live` announces the new period after each step. -->\n <div class=\"order-5 flex basis-full items-center justify-between gap-1 lg:order-2 lg:basis-auto lg:justify-start\">\n <button (click)=\"navigate(-1)\"\n [attr.aria-label]=\"config.previousLabel\"\n [data]=\"{ variant: 'text', size: 'md', color: 'gray' }\"\n class=\"shrink-0\"\n mnButton\n type=\"button\">\n <svg [size]=\"18\" lucideChevronLeft></svg>\n </button>\n\n <h2 aria-live=\"polite\" class=\"text-center text-base font-semibold whitespace-nowrap px-1 lg:min-w-56\">\n {{ periodLabel }}\n </h2>\n\n <button (click)=\"navigate(1)\"\n [attr.aria-label]=\"config.nextLabel\"\n [data]=\"{ variant: 'text', size: 'md', color: 'gray' }\"\n class=\"shrink-0\"\n mnButton\n type=\"button\">\n <svg [size]=\"18\" lucideChevronRight></svg>\n </button>\n </div>\n\n </div>\n\n <div class=\"grid grid-cols-1 lg:grid-cols-[1fr_220px] gap-3 flex-1 min-h-0\">\n <div [id]=\"panelId\" class=\"min-w-0 min-h-0 overflow-hidden overflow-y-auto\" role=\"tabpanel\">\n @if (currentView === CalendarView.MONTH) {\n <mn-calendar-month\n [focusDay]=\"focusDay\"\n [eventsChanged]=\"internalEventsChanged\"\n [focusDayChanged]=\"internalFocusDayChanged\"\n [config]=\"config\"\n (dayClicked)=\"onMonthDayClick($event)\">\n </mn-calendar-month>\n }\n @if (currentView === CalendarView.WEEK) {\n <mn-calendar-week\n [focusDay]=\"focusDay\"\n [eventsChanged]=\"internalEventsChanged\"\n [focusDayChanged]=\"internalFocusDayChanged\"\n [config]=\"config\"\n [calendarEventComponent]=\"CalendarEventComponent\"\n (eventClicked)=\"onEventClick($event)\">\n </mn-calendar-week>\n }\n @if (currentView === CalendarView.DAY) {\n <mn-calendar-day\n [focusDay]=\"focusDay\"\n [eventsChanged]=\"internalEventsChanged\"\n [focusDayChanged]=\"internalFocusDayChanged\"\n [config]=\"config\"\n [calendarEventComponent]=\"CalendarEventComponent\"\n (eventClicked)=\"onEventClick($event)\">\n </mn-calendar-day>\n }\n </div>\n <div class=\"hidden lg:block border-l border-base-300 overflow-auto\">\n <mn-upcoming-events\n [eventsChanged]=\"internalEventsChanged\"\n [config]=\"config\"\n (eventClicked)=\"onEventClick($event)\">\n </mn-upcoming-events>\n </div>\n </div>\n\n</div>\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100%}\n"] }]
|
|
9351
9457
|
}], ctorParameters: () => [], propDecorators: { showButton: [{
|
|
9352
9458
|
type: Input
|
|
9353
9459
|
}], buttonTitle: [{
|