mn-angular-lib 1.0.113 → 1.0.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, inject, HostBinding, Input, Component, ChangeDetectionStrategy, ApplicationRef, APP_INITIALIZER, Pipe, signal, DestroyRef, Optional, SkipSelf, Attribute, Directive, ElementRef, EventEmitter, computed, Output, input, output, Renderer2, HostListener, ViewChild, ViewContainerRef, forwardRef, ChangeDetectorRef, afterEveryRender, TemplateRef, ViewChildren, viewChild, EnvironmentInjector, createComponent, isSignal } from '@angular/core';
2
+ import { InjectionToken, Injectable, inject, HostBinding, Input, Component, ChangeDetectionStrategy, ApplicationRef, APP_INITIALIZER, Pipe, signal, DestroyRef, Optional, SkipSelf, Attribute, Directive, ElementRef, EventEmitter, computed, Output, input, output, Injector, viewChildren, linkedSignal, afterNextRender, Renderer2, HostListener, ViewChild, ViewContainerRef, forwardRef, ChangeDetectorRef, afterEveryRender, TemplateRef, ViewChildren, viewChild, EnvironmentInjector, createComponent, isSignal } from '@angular/core';
3
3
  export { TemplateRef, Type } from '@angular/core';
4
- import { BehaviorSubject, firstValueFrom, skip, fromEvent, Subject, debounceTime as debounceTime$1, of, takeUntil, map, catchError } from 'rxjs';
4
+ import { BehaviorSubject, firstValueFrom, skip, Subject, debounceTime, of, takeUntil, map, catchError } from 'rxjs';
5
5
  import * as i1 from '@angular/common';
6
6
  import { CommonModule, NgClass, NgOptimizedImage, NgTemplateOutlet } from '@angular/common';
7
7
  import { tv } from 'tailwind-variants';
@@ -10,7 +10,6 @@ import { NgControl, Validators, FormsModule, NG_VALUE_ACCESSOR, FormBuilder, Rea
10
10
  import { HttpClient, HttpErrorResponse, HttpStatusCode, HttpParams } from '@angular/common/http';
11
11
  import JSON5 from 'json5';
12
12
  import { LucideFile, LucideImagePlus, LucideTrash2, LucideUpload, LucideX, LucideChevronLeft, LucideChevronRight, LucideArrowLeft, LucideArrowRight, LucideCheck, LucideFilter, LucideFunnel, LucideDynamicIcon } from '@lucide/angular';
13
- import { debounceTime } from 'rxjs/operators';
14
13
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
15
14
  import { DomSanitizer } from '@angular/platform-browser';
16
15
 
@@ -2916,41 +2915,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
2916
2915
  args: [{ required: true }]
2917
2916
  }] } });
2918
2917
 
2918
+ /** A full week: the most days the strip shows, and the size it aligns to. */
2919
+ const DAYS_IN_WEEK = 7;
2920
+ /** Fewest days worth showing before the picker takes over instead. */
2921
+ const MIN_TILE_COUNT = 3;
2922
+ /** `Date.getDay()` value for Monday. */
2923
+ const MONDAY = 1;
2919
2924
  /**
2920
- * Custom day-tile breakpoint ladder, evaluated from widest to narrowest so the
2921
- * first matching {@link TileBreakpoint.minWidth} wins.
2922
- *
2923
- * The counts are tuned to the bar's two-layout responsive design. Below the
2924
- * template's `xl` breakpoint (1280px) the bar stacks and the day strip gets its
2925
- * own full-width row, so it can afford more tiles; at/above `xl` everything sits on
2926
- * one row alongside the Today button, arrows, divider and date picker (≥1500px);
2927
- * below that the strip sits on its own full-width row, so a full week fits without
2928
- * crowding. The steps:
2929
- * - ≥ 1028px: 7 tiles — the strip's own row (or the wide single-row layout) has
2930
- * room for a full week.
2931
- * - 640–1027px: 5 tiles — small tablets / large phones.
2932
- * - below 640px: 3 tiles — phones stay compact and page via the arrows.
2933
- *
2934
- * Note: below {@link MnDateSelectorBar.MIN_STRIP_WIDTH}px the strip is hidden
2935
- * entirely (only Today + the date picker remain), so those counts never render.
2925
+ * Width one day occupies. Days are sized by their content: a three-letter weekday
2926
+ * and a two-digit number at their set sizes measure about 72px including padding,
2927
+ * plus the `gap-1` beside it. Rounded up, because coming in under the real width
2928
+ * overflows the strip into the arrow beside it.
2936
2929
  */
2937
- const TILE_BREAKPOINTS = [
2938
- { minWidth: 1028, tiles: 7 },
2939
- { minWidth: 640, tiles: 5 },
2940
- { minWidth: 0, tiles: 3 },
2941
- ];
2930
+ const TILE_SLOT_WIDTH = 80;
2931
+ /** Space reserved for the two borderless window arrows and their gaps. */
2932
+ const ARROWS_ZONE_WIDTH = 88;
2933
+ /**
2934
+ * Space the Today button claims, including the bar's own horizontal padding and
2935
+ * the gap beside it.
2936
+ */
2937
+ const CONTROLS_ZONE_WIDTH = 150;
2938
+ /** Space the month caption claims beside the strip. */
2939
+ const MONTH_CAPTION_WIDTH = 88;
2940
+ /** Everything on the row that isn't a day tile. */
2941
+ const RESERVED_WIDTH = ARROWS_ZONE_WIDTH + CONTROLS_ZONE_WIDTH + MONTH_CAPTION_WIDTH;
2942
+ /** Below this the strip can't hold even {@link MIN_TILE_COUNT} days. */
2943
+ const COMPACT_MAX_WIDTH = MIN_TILE_COUNT * TILE_SLOT_WIDTH + RESERVED_WIDTH;
2944
+ /** Assumed width before the first measurement (and during server-side rendering). */
2945
+ const UNMEASURED_WIDTH = 960;
2946
+ /** Counter used to give each bar instance a unique date-picker id. */
2947
+ let instanceCounter = 0;
2942
2948
  /**
2943
2949
  * Reusable, responsive date selector bar.
2944
2950
  *
2945
- * Renders a "Today" button, previous/next day-window arrows, a strip of day
2946
- * tiles (3 5 7 as the viewport widens; see {@link TILE_BREAKPOINTS}) and a
2947
- * date picker. Selecting a
2948
- * tile, pressing "Today", or picking a date through the picker emits the chosen
2949
- * day via {@link dateSelected}. The arrows only shift the visible tile window and
2950
- * never emit.
2951
+ * Renders a "Today" button, a month caption, previous/next arrows and a strip of
2952
+ * day tiles. Selecting a tile or pressing "Today" emits the chosen day via
2953
+ * {@link dateSelected}. The arrows only shift the visible days and never emit.
2951
2954
  *
2952
- * The component carries no hard-coded copy: button/placeholder text is supplied
2953
- * through {@link todayLabel} / {@link pickDateLabel}, and day/month names follow
2955
+ * Given room, the strip is a whole week running Monday to Sunday, so the weekday
2956
+ * columns hold still as you page and no weekday appears twice. As the bar narrows
2957
+ * it shows fewer days rather than adding a second row — a partial strip has no
2958
+ * week to align to, so it slides to keep the selection in view instead. Narrower
2959
+ * still, and the days give way to a date picker beside Today, which keeps every
2960
+ * date reachable on a phone rather than leaving a strip too cramped to use.
2961
+ *
2962
+ * Selecting a day already on the strip leaves it exactly where it is; a selection
2963
+ * from outside moves the strip to where that day is. The arrows page by whatever
2964
+ * is on show, without touching the selection.
2965
+ *
2966
+ * The component carries no hard-coded copy: button, placeholder and assistive
2967
+ * text are supplied through the label inputs, and day/month names follow
2954
2968
  * {@link locale} (falling back to the active {@link MnLanguageService} locale).
2955
2969
  *
2956
2970
  * @example
@@ -2965,18 +2979,18 @@ const TILE_BREAKPOINTS = [
2965
2979
  * ```
2966
2980
  */
2967
2981
  class MnDateSelectorBar {
2968
- /**
2969
- * Minimum viewport width (px) at which the day strip (arrows + tiles) is shown.
2970
- * On narrower screens there isn't room for it, so only the Today button and the
2971
- * date picker remain.
2972
- */
2973
- static MIN_STRIP_WIDTH = 320;
2974
2982
  /** The currently selected date, provided by the parent. */
2975
2983
  selectedDate = input(this.getToday(), ...(ngDevMode ? [{ debugName: "selectedDate" }] : []));
2976
2984
  /** Label for the "Today" button. */
2977
2985
  todayLabel = input('Today', ...(ngDevMode ? [{ debugName: "todayLabel" }] : []));
2978
- /** Placeholder for the date picker input. */
2986
+ /** Placeholder for the date picker shown in place of a too-cramped week strip. */
2979
2987
  pickDateLabel = input('Pick a date', ...(ngDevMode ? [{ debugName: "pickDateLabel" }] : []));
2988
+ /** Accessible name for the previous-week arrow. */
2989
+ previousLabel = input('Show the previous week', ...(ngDevMode ? [{ debugName: "previousLabel" }] : []));
2990
+ /** Accessible name for the next-week arrow. */
2991
+ nextLabel = input('Show the next week', ...(ngDevMode ? [{ debugName: "nextLabel" }] : []));
2992
+ /** Accessible name for the day strip as a whole. */
2993
+ dayStripLabel = input('Select a day', ...(ngDevMode ? [{ debugName: "dayStripLabel" }] : []));
2980
2994
  /**
2981
2995
  * BCP 47 locale used to format day/month names. When empty, the active
2982
2996
  * {@link MnLanguageService} locale is used.
@@ -2984,89 +2998,181 @@ class MnDateSelectorBar {
2984
2998
  locale = input('', ...(ngDevMode ? [{ debugName: "locale" }] : []));
2985
2999
  /** Emits when the user selects a new date. */
2986
3000
  dateSelected = output();
2987
- /** The selected date formatted as YYYY-MM-DD for the date-picker input. */
2988
- selectedDateString = computed(() => {
2989
- const d = this.selectedDate();
2990
- const pad = (n) => n.toString().padStart(2, '0');
2991
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
2992
- }, ...(ngDevMode ? [{ debugName: "selectedDateString" }] : []));
3001
+ /** Unique id for this instance's date picker, so several bars can coexist. */
3002
+ pickerId = `mn-dsb-date-${++instanceCounter}`;
2993
3003
  destroyRef = inject(DestroyRef);
3004
+ injector = inject(Injector);
3005
+ host = inject((ElementRef));
2994
3006
  lang = inject(MnLanguageService);
2995
- /** Current viewport width in pixels, tracked so the tile count stays responsive. */
2996
- viewportWidth = signal(this.getViewportWidth(), ...(ngDevMode ? [{ debugName: "viewportWidth" }] : []));
2997
- /**
2998
- * Whether the day strip (arrows + day tiles) is shown. Below
2999
- * {@link MnDateSelectorBar.MIN_STRIP_WIDTH} it is hidden, leaving only the Today
3000
- * button and the date picker.
3001
- */
3002
- showDayStrip = computed(() => this.viewportWidth() >= MnDateSelectorBar.MIN_STRIP_WIDTH, ...(ngDevMode ? [{ debugName: "showDayStrip" }] : []));
3003
- /** Start offset (in days from today) for the visible day-tile window. */
3004
- dayOffset = signal(0, ...(ngDevMode ? [{ debugName: "dayOffset" }] : []));
3007
+ tileButtons = viewChildren('tileButton', ...(ngDevMode ? [{ debugName: "tileButtons" }] : []));
3008
+ /** The bar's own width in px, tracked so the layout follows its container. */
3009
+ containerWidth = signal(0, ...(ngDevMode ? [{ debugName: "containerWidth" }] : []));
3005
3010
  /** Bumped whenever the active language changes so name formatting re-runs. */
3006
3011
  localeTick = signal(0, ...(ngDevMode ? [{ debugName: "localeTick" }] : []));
3012
+ /** How the bar is arranged at the current width. */
3013
+ layout = computed(() => (this.containerWidth() || UNMEASURED_WIDTH) >= COMPACT_MAX_WIDTH ? 'inline' : 'compact', ...(ngDevMode ? [{ debugName: "layout" }] : []));
3014
+ /** Whether the day strip is shown, or the picker has taken its place. */
3015
+ showDayStrip = computed(() => this.layout() === 'inline', ...(ngDevMode ? [{ debugName: "showDayStrip" }] : []));
3007
3016
  /**
3008
- * Number of day tiles to render, resolved from {@link TILE_BREAKPOINTS} against
3009
- * the current viewport width (3 5 7 as the screen widens).
3017
+ * Days in the visible strip: a full week where there's room, fewer as the bar
3018
+ * narrows. The bar drops days rather than adding a second row, so it stays one
3019
+ * line at every width it can.
3010
3020
  */
3011
3021
  tileCount = computed(() => {
3012
- const width = this.viewportWidth();
3013
- return (TILE_BREAKPOINTS.find((bp) => width >= bp.minWidth)?.tiles ??
3014
- TILE_BREAKPOINTS[TILE_BREAKPOINTS.length - 1].tiles);
3022
+ if (!this.showDayStrip())
3023
+ return 0;
3024
+ const width = this.containerWidth() || UNMEASURED_WIDTH;
3025
+ const fits = Math.floor((width - RESERVED_WIDTH) / TILE_SLOT_WIDTH);
3026
+ return Math.min(DAYS_IN_WEEK, Math.max(MIN_TILE_COUNT, fits));
3015
3027
  }, ...(ngDevMode ? [{ debugName: "tileCount" }] : []));
3016
3028
  /** Effective locale: explicit input, else the active app locale. */
3017
3029
  effectiveLocale = computed(() => {
3018
3030
  this.localeTick();
3019
3031
  return this.locale() || this.lang.locale;
3020
3032
  }, ...(ngDevMode ? [{ debugName: "effectiveLocale" }] : []));
3021
- /** The visible day tiles, derived from the selected date, offset and viewport. */
3033
+ /**
3034
+ * First day of the visible strip — the week's Monday when a whole week is on
3035
+ * show, otherwise whatever start keeps the selection in view.
3036
+ *
3037
+ * The strip holds still while the selection stays on screen; a selection
3038
+ * outside it moves to where that day is. The arrows write here directly to page
3039
+ * away from the selection.
3040
+ */
3041
+ windowStart = linkedSignal({ ...(ngDevMode ? { debugName: "windowStart" } : {}), source: () => ({ selected: this.selectedDate().getTime(), count: this.tileCount() }),
3042
+ computation: (source, previous) => {
3043
+ const selected = new Date(source.selected);
3044
+ const { count } = source;
3045
+ const anchored = this.anchorFor(selected, count);
3046
+ if (!previous || count === 0)
3047
+ return anchored;
3048
+ const current = previous.value;
3049
+ const holdsSelection = (() => {
3050
+ const offset = this.daysBetween(current, selected);
3051
+ return offset >= 0 && offset < count;
3052
+ })();
3053
+ // A whole week must also still be Monday-aligned to be worth keeping —
3054
+ // otherwise a strip that grew back to seven would keep a stale start.
3055
+ const stillValid = count === DAYS_IN_WEEK
3056
+ ? holdsSelection && current.getDay() === MONDAY
3057
+ : holdsSelection;
3058
+ return stillValid ? current : anchored;
3059
+ } });
3060
+ /**
3061
+ * Index of the tile that is currently keyboard-reachable (roving tabindex).
3062
+ * Tracks the selection so Tab lands on the selected day, falling back to Monday
3063
+ * when the selection has been paged out of sight.
3064
+ */
3065
+ focusedIndex = linkedSignal({ ...(ngDevMode ? { debugName: "focusedIndex" } : {}), source: () => this.selectedDate().getTime(),
3066
+ computation: () => Math.max(0, this.indexOfSelected()) });
3067
+ /** The visible days, derived from the window start and the current selection. */
3022
3068
  dayTiles = computed(() => {
3023
- const today = this.getToday();
3024
- const selected = this.selectedDate();
3025
- const offset = this.dayOffset();
3026
3069
  const count = this.tileCount();
3070
+ if (count === 0)
3071
+ return [];
3072
+ const start = this.windowStart();
3073
+ const selected = this.selectedDate();
3074
+ const today = this.getToday();
3027
3075
  const locale = this.effectiveLocale();
3028
3076
  const tiles = [];
3077
+ let previousMonth = -1;
3078
+ let previousYear = -1;
3029
3079
  for (let i = 0; i < count; i++) {
3030
- const date = new Date(today);
3031
- date.setDate(today.getDate() + offset + i);
3080
+ const date = new Date(start);
3081
+ date.setDate(start.getDate() + i);
3082
+ const month = date.getMonth();
3083
+ const year = date.getFullYear();
3084
+ const startsNewMonth = i > 0 && (month !== previousMonth || year !== previousYear);
3032
3085
  tiles.push({
3033
3086
  date,
3034
3087
  dayName: date.toLocaleDateString(locale, { weekday: 'short' }),
3035
3088
  dayNumber: date.getDate(),
3036
3089
  monthName: date.toLocaleDateString(locale, { month: 'short' }),
3090
+ startsNewMonth,
3037
3091
  isSelected: this.isSameDay(date, selected),
3038
3092
  isToday: this.isSameDay(date, today),
3093
+ accessibleLabel: date.toLocaleDateString(locale, {
3094
+ weekday: 'long',
3095
+ day: 'numeric',
3096
+ month: 'long',
3097
+ year: 'numeric',
3098
+ }),
3039
3099
  });
3100
+ previousMonth = month;
3101
+ previousYear = year;
3040
3102
  }
3041
3103
  return tiles;
3042
3104
  }, ...(ngDevMode ? [{ debugName: "dayTiles" }] : []));
3105
+ /**
3106
+ * Names the month the strip is currently in, so the days are never just loose
3107
+ * numbers. Reads as a range when the strip straddles two months, and carries
3108
+ * both years when it straddles two of those.
3109
+ */
3110
+ monthCaption = computed(() => {
3111
+ const locale = this.effectiveLocale();
3112
+ const tiles = this.dayTiles();
3113
+ // With no strip to describe — the compact layout — the caption names the
3114
+ // month the selection sits in, so the bar is never unlabelled.
3115
+ if (!tiles.length) {
3116
+ return this.selectedDate().toLocaleDateString(locale, { month: 'short', year: 'numeric' });
3117
+ }
3118
+ const first = tiles[0].date;
3119
+ const last = tiles[tiles.length - 1].date;
3120
+ if (first.getFullYear() !== last.getFullYear()) {
3121
+ const from = first.toLocaleDateString(locale, { month: 'short', year: 'numeric' });
3122
+ const to = last.toLocaleDateString(locale, { month: 'short', year: 'numeric' });
3123
+ return `${from} – ${to}`;
3124
+ }
3125
+ if (first.getMonth() !== last.getMonth()) {
3126
+ const from = first.toLocaleDateString(locale, { month: 'short' });
3127
+ const to = last.toLocaleDateString(locale, { month: 'short', year: 'numeric' });
3128
+ return `${from} – ${to}`;
3129
+ }
3130
+ return first.toLocaleDateString(locale, { month: 'short', year: 'numeric' });
3131
+ }, ...(ngDevMode ? [{ debugName: "monthCaption" }] : []));
3132
+ /** The selected date formatted as YYYY-MM-DD for the date-picker input. */
3133
+ selectedDateString = computed(() => {
3134
+ const d = this.selectedDate();
3135
+ const pad = (n) => n.toString().padStart(2, '0');
3136
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
3137
+ }, ...(ngDevMode ? [{ debugName: "selectedDateString" }] : []));
3138
+ /**
3139
+ * Props for the compact-layout date picker. Sized to match the Today button it
3140
+ * sits beside, and filling the rest of the row so it stays an easy tap target.
3141
+ */
3142
+ pickerProps = computed(() => ({
3143
+ id: this.pickerId,
3144
+ mode: 'date',
3145
+ placeholder: this.pickDateLabel(),
3146
+ size: 'md',
3147
+ borderRadius: 'lg',
3148
+ hover: true,
3149
+ fullWidth: true,
3150
+ }), ...(ngDevMode ? [{ debugName: "pickerProps" }] : []));
3043
3151
  ngOnInit() {
3044
3152
  // Re-format names when the active language changes.
3045
3153
  this.lang.locale$
3046
3154
  .pipe(takeUntilDestroyed(this.destroyRef))
3047
3155
  .subscribe(() => this.localeTick.update((v) => v + 1));
3048
- // Track viewport width so the tile count stays responsive.
3049
- if (typeof window !== 'undefined') {
3050
- fromEvent(window, 'resize')
3051
- .pipe(debounceTime(100), takeUntilDestroyed(this.destroyRef))
3052
- .subscribe(() => this.viewportWidth.set(this.getViewportWidth()));
3053
- }
3156
+ this.observeOwnWidth();
3054
3157
  }
3055
- /** Shifts the visible window back by one tile-count without changing the selection. */
3158
+ /** Shows the days before the visible ones, without changing the selection. */
3056
3159
  navigatePrevious() {
3057
- this.dayOffset.update((o) => o - this.tileCount());
3160
+ this.shiftWindow(-1);
3058
3161
  }
3059
- /** Shifts the visible window forward by one tile-count without changing the selection. */
3162
+ /** Shows the days after the visible ones, without changing the selection. */
3060
3163
  navigateNext() {
3061
- this.dayOffset.update((o) => o + this.tileCount());
3164
+ this.shiftWindow(1);
3062
3165
  }
3063
- /** Resets the window to today and selects today. */
3166
+ /** Returns to today: brings today into the strip and selects it. */
3064
3167
  goToToday() {
3065
- this.dayOffset.set(0);
3066
- // Only emit when today isn't already the selected day, so pressing "Today"
3067
- // while on today doesn't trigger a redundant reload.
3068
- if (!this.isSameDay(this.getToday(), this.selectedDate())) {
3069
- this.dateSelected.emit(this.getToday());
3168
+ const today = this.getToday();
3169
+ // Move explicitly: when today is already selected the window signal has no new
3170
+ // source value to react to, but the user still expects to land on it.
3171
+ this.windowStart.set(this.anchorFor(today, this.tileCount()));
3172
+ // Only emit when today isn't already selected, so pressing "Today" while on
3173
+ // today doesn't trigger a redundant reload.
3174
+ if (!this.isSameDay(today, this.selectedDate())) {
3175
+ this.dateSelected.emit(today);
3070
3176
  }
3071
3177
  }
3072
3178
  /** Selects a date and emits it, unless it is already the selected day. */
@@ -3078,8 +3184,8 @@ class MnDateSelectorBar {
3078
3184
  this.dateSelected.emit(date);
3079
3185
  }
3080
3186
  /**
3081
- * Handles the date-picker model change: centres the window on the picked date
3082
- * and emits it (unless it is already the selected day).
3187
+ * Handles the date-picker model change: shows the picked day's week and emits
3188
+ * it (unless it is already the selected day).
3083
3189
  * @param value The date string in YYYY-MM-DD format.
3084
3190
  */
3085
3191
  onDateModelChanged(value) {
@@ -3088,21 +3194,126 @@ class MnDateSelectorBar {
3088
3194
  const picked = new Date(value + 'T00:00:00');
3089
3195
  if (isNaN(picked.getTime()))
3090
3196
  return;
3091
- const today = this.getToday();
3092
- const diffDays = Math.round((picked.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
3093
- // Centre the picked date in the visible tile window.
3094
- this.dayOffset.set(diffDays - Math.floor(this.tileCount() / 2));
3197
+ this.windowStart.set(this.anchorFor(picked, this.tileCount()));
3095
3198
  // Picking the day that's already selected shouldn't re-emit.
3096
3199
  if (!this.isSameDay(picked, this.selectedDate())) {
3097
3200
  this.dateSelected.emit(picked);
3098
3201
  }
3099
3202
  }
3203
+ /** Whether the tile at `index` is the one reachable with Tab. */
3204
+ isTabbable(index) {
3205
+ return index === this.focusedIndex();
3206
+ }
3207
+ /** Remembers which tile last held focus, so Tab returns to it. */
3208
+ onTileFocus(index) {
3209
+ this.focusedIndex.set(index);
3210
+ }
3211
+ /**
3212
+ * Moves focus across the week with the arrow keys. Running off either end turns
3213
+ * the page to the neighbouring week and lands on the day that continues the run,
3214
+ * so the weeks read as one continuous calendar.
3215
+ */
3216
+ onTileKeydown(event, index) {
3217
+ const lastIndex = this.tileCount() - 1;
3218
+ switch (event.key) {
3219
+ case 'ArrowRight':
3220
+ event.preventDefault();
3221
+ if (index < lastIndex) {
3222
+ this.moveFocusTo(index + 1);
3223
+ }
3224
+ else {
3225
+ // Off the end: turn the page and land on the day that continues the run.
3226
+ this.shiftWindow(1);
3227
+ this.moveFocusTo(0);
3228
+ }
3229
+ break;
3230
+ case 'ArrowLeft':
3231
+ event.preventDefault();
3232
+ if (index > 0) {
3233
+ this.moveFocusTo(index - 1);
3234
+ }
3235
+ else {
3236
+ this.shiftWindow(-1);
3237
+ this.moveFocusTo(lastIndex);
3238
+ }
3239
+ break;
3240
+ case 'Home':
3241
+ event.preventDefault();
3242
+ this.moveFocusTo(0);
3243
+ break;
3244
+ case 'End':
3245
+ event.preventDefault();
3246
+ this.moveFocusTo(lastIndex);
3247
+ break;
3248
+ default:
3249
+ break;
3250
+ }
3251
+ }
3252
+ /** trackBy key for day tiles. */
3253
+ trackByTile(_index, tile) {
3254
+ return tile.date.getTime();
3255
+ }
3100
3256
  /**
3101
- * Returns the current viewport width in pixels, falling back to a desktop-ish
3102
- * width when there is no window (e.g. server-side rendering).
3257
+ * Moves the strip by `pages` of whatever it is currently showing. Paging by the
3258
+ * visible count is what keeps a full week Monday-aligned — seven days forward
3259
+ * from a Monday is the next Monday.
3103
3260
  */
3104
- getViewportWidth() {
3105
- return typeof window === 'undefined' ? 1280 : window.innerWidth;
3261
+ shiftWindow(pages) {
3262
+ const next = new Date(this.windowStart());
3263
+ next.setDate(next.getDate() + pages * this.tileCount());
3264
+ this.windowStart.set(next);
3265
+ }
3266
+ /** Focuses the tile at `index` once the week has rendered. */
3267
+ moveFocusTo(index) {
3268
+ this.focusedIndex.set(index);
3269
+ afterNextRender(() => this.tileButtons()[index]?.nativeElement.focus(), { injector: this.injector });
3270
+ }
3271
+ /** Index of the selected day within the visible week, or -1 when off-week. */
3272
+ indexOfSelected() {
3273
+ return this.dayTiles().findIndex((tile) => tile.isSelected);
3274
+ }
3275
+ /**
3276
+ * Where the strip should start to show `date` among `count` days: the week's
3277
+ * Monday when a whole week is on show, otherwise centred on the day, since a
3278
+ * partial strip has no week to align to.
3279
+ */
3280
+ anchorFor(date, count) {
3281
+ if (count === DAYS_IN_WEEK)
3282
+ return this.startOfWeek(date);
3283
+ const start = new Date(date);
3284
+ start.setHours(0, 0, 0, 0);
3285
+ start.setDate(start.getDate() - Math.floor(count / 2));
3286
+ return start;
3287
+ }
3288
+ /**
3289
+ * Returns the Monday of the week containing `date`. `getDay()` counts from
3290
+ * Sunday, so the shift maps Sunday to the end of the week rather than the start.
3291
+ */
3292
+ startOfWeek(date) {
3293
+ const start = new Date(date);
3294
+ start.setHours(0, 0, 0, 0);
3295
+ start.setDate(start.getDate() - ((start.getDay() + 6) % 7));
3296
+ return start;
3297
+ }
3298
+ /** Whole calendar days from `from` to `to`, ignoring time of day and DST. */
3299
+ daysBetween(from, to) {
3300
+ const a = Date.UTC(from.getFullYear(), from.getMonth(), from.getDate());
3301
+ const b = Date.UTC(to.getFullYear(), to.getMonth(), to.getDate());
3302
+ return Math.round((b - a) / 86400000);
3303
+ }
3304
+ /** Tracks the bar's own width so the layout responds to its container. */
3305
+ observeOwnWidth() {
3306
+ const element = this.host.nativeElement;
3307
+ this.containerWidth.set(element.getBoundingClientRect().width);
3308
+ if (typeof ResizeObserver === 'undefined')
3309
+ return;
3310
+ const observer = new ResizeObserver((entries) => {
3311
+ const width = entries[0]?.contentRect.width ?? 0;
3312
+ if (width > 0)
3313
+ this.containerWidth.set(width);
3314
+ });
3315
+ observer.observe(element);
3316
+ this.destroyRef.onDestroy(() => observer.disconnect());
3106
3317
  }
3107
3318
  /** Returns a Date object for today at midnight. */
3108
3319
  getToday() {
@@ -3117,7 +3328,7 @@ class MnDateSelectorBar {
3117
3328
  a.getDate() === b.getDate());
3118
3329
  }
3119
3330
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnDateSelectorBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
3120
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnDateSelectorBar, isStandalone: true, selector: "mn-date-selector-bar", inputs: { selectedDate: { classPropertyName: "selectedDate", publicName: "selectedDate", isSignal: true, isRequired: false, transformFunction: null }, todayLabel: { classPropertyName: "todayLabel", publicName: "todayLabel", isSignal: true, isRequired: false, transformFunction: null }, pickDateLabel: { classPropertyName: "pickDateLabel", publicName: "pickDateLabel", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateSelected: "dateSelected" }, ngImport: i0, template: "<!-- Date selector bar -->\n<div\n class=\"flex flex-col min-[1500px]:flex-row min-[1500px]:items-center justify-center rounded-xl px-4 py-5 gap-x-6 gap-y-6 overflow-hidden\">\n <!-- Top row on mobile/tablet: Today + Date picker side by side -->\n <div class=\"flex min-[1500px]:contents items-center gap-3\">\n <!-- Today button -->\n <button (click)=\"goToToday()\" [data]=\"{ variant: 'fill', color: 'primary', size: 'lg' }\"\n class=\"flex-1 min-[1500px]:flex-none\"\n mnButton>\n {{ todayLabel() }}\n </button>\n\n <!-- Date picker (visible on mobile/tablet in top row, moves to end on desktop).\n Wrapped in a div that owns the responsive hide: with fullWidth the datetime sets an\n inline `display:block` on its host, which would override `min-[1500px]:hidden` and leave it\n visible on desktop (showing two pickers). Hiding the wrapper avoids that. -->\n <div class=\"min-[1500px]:hidden flex-1 min-w-0\">\n <mn-lib-datetime (ngModelChange)=\"onDateModelChanged($event)\"\n [ngModel]=\"selectedDateString()\"\n [props]=\"{ id: 'mn-dsb-date-mobile', mode: 'date', placeholder: pickDateLabel(), size: 'lg', borderRadius: 'xl', hover: true, fullWidth: true }\"\n class=\"block w-full\"></mn-lib-datetime>\n </div>\n </div>\n\n <!-- Date navigation (arrows + day tiles). Hidden on very narrow screens\n (< MIN_STRIP_WIDTH), where only the Today button and date picker remain. -->\n @if (showDayStrip()) {\n <div class=\"flex items-center gap-4 overflow-x-auto mx-auto min-[1500px]:mx-0 pb-2 max-w-full\">\n <!-- Left arrow: shifts the visible day window backward -->\n <button (click)=\"navigatePrevious()\" [data]=\"{ variant: 'outline', size: 'md', color: 'gray' }\" class=\"shrink-0\"\n mnButton>\n <svg [size]=\"18\" lucideChevronLeft></svg>\n </button>\n\n <!-- Day tiles -->\n @for (tile of dayTiles(); track tile.date.getTime()) {\n <button\n (click)=\"selectDate(tile.date)\"\n [class.bg-primary]=\"tile.isSelected\"\n [class.hover:bg-base-200]=\"!tile.isSelected\"\n [class.text-primary-content]=\"tile.isSelected\"\n class=\"flex flex-col items-center min-w-12 min-[1500px]:min-w-16 px-2 min-[1500px]:px-3 py-2 rounded-lg transition-all cursor-pointer shrink-0\"\n >\n <span class=\"text-xs font-medium uppercase\">{{ tile.dayName }}</span>\n <span class=\"text-base min-[1500px]:text-lg font-bold\">{{ tile.dayNumber }}</span>\n </button>\n }\n\n <!-- Right arrow: shifts the visible day window forward -->\n <button (click)=\"navigateNext()\" [data]=\"{ variant: 'outline', size: 'md', color: 'gray' }\" class=\"shrink-0\"\n mnButton>\n <svg [size]=\"18\" lucideChevronRight></svg>\n </button>\n </div>\n }\n\n <!-- Vertical divider (hidden on mobile/tablet) -->\n <div class=\"hidden min-[1500px]:block w-px h-10 bg-base-300\"></div>\n\n <!-- Date picker (desktop only, hidden on mobile/tablet since it's in the top row) -->\n <mn-lib-datetime (ngModelChange)=\"onDateModelChanged($event)\"\n [ngModel]=\"selectedDateString()\"\n [props]=\"{ id: 'mn-dsb-date-desktop', mode: 'date', placeholder: pickDateLabel(), size: 'lg', borderRadius: 'xl', hover: true }\"\n class=\"hidden min-[1500px]:block\"></mn-lib-datetime>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3331
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnDateSelectorBar, isStandalone: true, selector: "mn-date-selector-bar", inputs: { selectedDate: { classPropertyName: "selectedDate", publicName: "selectedDate", isSignal: true, isRequired: false, transformFunction: null }, todayLabel: { classPropertyName: "todayLabel", publicName: "todayLabel", isSignal: true, isRequired: false, transformFunction: null }, pickDateLabel: { classPropertyName: "pickDateLabel", publicName: "pickDateLabel", isSignal: true, isRequired: false, transformFunction: null }, previousLabel: { classPropertyName: "previousLabel", publicName: "previousLabel", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, dayStripLabel: { classPropertyName: "dayStripLabel", publicName: "dayStripLabel", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateSelected: "dateSelected" }, host: { classAttribute: "block" }, viewQueries: [{ propertyName: "tileButtons", predicate: ["tileButton"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Date selector bar. Layout is driven by the bar's own measured width, so it\n rearranges for the space the bar was given, not for the viewport. It never\n stacks: as room runs out it shows fewer days, then hands over to the picker.\n Nothing here is boxed \u2014 spacing does the grouping, not borders. -->\n<div class=\"flex items-center gap-4 px-4 py-3\">\n\n <!-- Today: returns the strip and the selection to the current day, and the\n control people reach for most, so it carries the weight of a filled button. -->\n <button (click)=\"goToToday()\"\n [data]=\"{ variant: 'fill', color: 'primary', size: 'md' }\"\n class=\"shrink-0\"\n mnButton\n type=\"button\">\n {{ todayLabel() }}\n </button>\n\n <!-- Too narrow for a usable strip, so the picker takes its place beside Today.\n Every date stays reachable on a phone, in one tap rather than by paging.\n The caption comes along, so the compact bar is labelled like the wide one \u2014\n it names the selected day's month rather than a visible range. -->\n @if (!showDayStrip()) {\n <span aria-hidden=\"true\"\n class=\"shrink-0 text-xs font-semibold tracking-wide whitespace-nowrap uppercase opacity-55\">\n {{ monthCaption() }}\n </span>\n\n <mn-lib-datetime (ngModelChange)=\"onDateModelChanged($event)\"\n [ngModel]=\"selectedDateString()\"\n [props]=\"pickerProps()\"\n class=\"block min-w-0 flex-1\"></mn-lib-datetime>\n }\n\n <!-- The strip takes the room left over and centres its contents in it, so on a\n wide bar the days sit in the middle rather than trailing off to one side. -->\n @if (showDayStrip()) {\n <div [attr.aria-label]=\"dayStripLabel()\"\n class=\"flex min-w-0 flex-1 items-center justify-center gap-3\"\n role=\"group\">\n <!-- Names the month the strip is in, so the day numbers are never loose. -->\n <span aria-hidden=\"true\"\n class=\"shrink-0 text-xs font-semibold tracking-wide whitespace-nowrap uppercase opacity-55\">\n {{ monthCaption() }}\n </span>\n\n <button (click)=\"navigatePrevious()\"\n [attr.aria-label]=\"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 <!-- Days read on one line so each sits at the same height as the buttons\n either side of it. Roving tabindex: one day is in the tab order, the\n arrow keys move between the rest and page the strip at either end. -->\n <div class=\"flex min-w-0 gap-1\">\n @for (tile of dayTiles(); track tile.date.getTime(); let i = $index) {\n <!-- A month break is marked by extra breathing room rather than a rule,\n so the run of days stays unbroken; the caption names both months. -->\n <button #tileButton\n (click)=\"selectDate(tile.date)\"\n (focus)=\"onTileFocus(i)\"\n (keydown)=\"onTileKeydown($event, i)\"\n [attr.aria-current]=\"tile.isToday ? 'date' : null\"\n [attr.aria-label]=\"tile.accessibleLabel\"\n [attr.aria-pressed]=\"tile.isSelected\"\n [attr.tabindex]=\"isTabbable(i) ? 0 : -1\"\n [class.bg-primary]=\"tile.isSelected\"\n [class.hover:bg-base-200]=\"!tile.isSelected\"\n [class.ml-3]=\"tile.startsNewMonth\"\n [class.text-primary-content]=\"tile.isSelected\"\n class=\"flex min-w-16 shrink-0 cursor-pointer items-center justify-center gap-1.5 rounded-lg px-3 py-2 text-sm transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary motion-reduce:transition-none\"\n type=\"button\">\n <span [class.opacity-60]=\"!tile.isSelected\"\n [class.opacity-90]=\"tile.isSelected\"\n aria-hidden=\"true\"\n class=\"text-xs font-medium uppercase\">\n {{ tile.dayName }}\n </span>\n <span aria-hidden=\"true\" class=\"font-bold tabular-nums\">{{ tile.dayNumber }}</span>\n <!-- Today's dot. Every day reserves the space so no tile changes\n width; only its colour says which one is today, and exactly one\n colour class applies at a time so they can't fight. -->\n <span [class.bg-primary-content]=\"tile.isToday && tile.isSelected\"\n [class.bg-primary]=\"tile.isToday && !tile.isSelected\"\n aria-hidden=\"true\"\n class=\"size-1 shrink-0 rounded-full\"></span>\n </button>\n }\n </div>\n\n <button (click)=\"navigateNext()\"\n [attr.aria-label]=\"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", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3121
3332
  }
3122
3333
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnDateSelectorBar, decorators: [{
3123
3334
  type: Component,
@@ -3128,8 +3339,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3128
3339
  MnDatetime,
3129
3340
  LucideChevronLeft,
3130
3341
  LucideChevronRight,
3131
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Date selector bar -->\n<div\n class=\"flex flex-col min-[1500px]:flex-row min-[1500px]:items-center justify-center rounded-xl px-4 py-5 gap-x-6 gap-y-6 overflow-hidden\">\n <!-- Top row on mobile/tablet: Today + Date picker side by side -->\n <div class=\"flex min-[1500px]:contents items-center gap-3\">\n <!-- Today button -->\n <button (click)=\"goToToday()\" [data]=\"{ variant: 'fill', color: 'primary', size: 'lg' }\"\n class=\"flex-1 min-[1500px]:flex-none\"\n mnButton>\n {{ todayLabel() }}\n </button>\n\n <!-- Date picker (visible on mobile/tablet in top row, moves to end on desktop).\n Wrapped in a div that owns the responsive hide: with fullWidth the datetime sets an\n inline `display:block` on its host, which would override `min-[1500px]:hidden` and leave it\n visible on desktop (showing two pickers). Hiding the wrapper avoids that. -->\n <div class=\"min-[1500px]:hidden flex-1 min-w-0\">\n <mn-lib-datetime (ngModelChange)=\"onDateModelChanged($event)\"\n [ngModel]=\"selectedDateString()\"\n [props]=\"{ id: 'mn-dsb-date-mobile', mode: 'date', placeholder: pickDateLabel(), size: 'lg', borderRadius: 'xl', hover: true, fullWidth: true }\"\n class=\"block w-full\"></mn-lib-datetime>\n </div>\n </div>\n\n <!-- Date navigation (arrows + day tiles). Hidden on very narrow screens\n (< MIN_STRIP_WIDTH), where only the Today button and date picker remain. -->\n @if (showDayStrip()) {\n <div class=\"flex items-center gap-4 overflow-x-auto mx-auto min-[1500px]:mx-0 pb-2 max-w-full\">\n <!-- Left arrow: shifts the visible day window backward -->\n <button (click)=\"navigatePrevious()\" [data]=\"{ variant: 'outline', size: 'md', color: 'gray' }\" class=\"shrink-0\"\n mnButton>\n <svg [size]=\"18\" lucideChevronLeft></svg>\n </button>\n\n <!-- Day tiles -->\n @for (tile of dayTiles(); track tile.date.getTime()) {\n <button\n (click)=\"selectDate(tile.date)\"\n [class.bg-primary]=\"tile.isSelected\"\n [class.hover:bg-base-200]=\"!tile.isSelected\"\n [class.text-primary-content]=\"tile.isSelected\"\n class=\"flex flex-col items-center min-w-12 min-[1500px]:min-w-16 px-2 min-[1500px]:px-3 py-2 rounded-lg transition-all cursor-pointer shrink-0\"\n >\n <span class=\"text-xs font-medium uppercase\">{{ tile.dayName }}</span>\n <span class=\"text-base min-[1500px]:text-lg font-bold\">{{ tile.dayNumber }}</span>\n </button>\n }\n\n <!-- Right arrow: shifts the visible day window forward -->\n <button (click)=\"navigateNext()\" [data]=\"{ variant: 'outline', size: 'md', color: 'gray' }\" class=\"shrink-0\"\n mnButton>\n <svg [size]=\"18\" lucideChevronRight></svg>\n </button>\n </div>\n }\n\n <!-- Vertical divider (hidden on mobile/tablet) -->\n <div class=\"hidden min-[1500px]:block w-px h-10 bg-base-300\"></div>\n\n <!-- Date picker (desktop only, hidden on mobile/tablet since it's in the top row) -->\n <mn-lib-datetime (ngModelChange)=\"onDateModelChanged($event)\"\n [ngModel]=\"selectedDateString()\"\n [props]=\"{ id: 'mn-dsb-date-desktop', mode: 'date', placeholder: pickDateLabel(), size: 'lg', borderRadius: 'xl', hover: true }\"\n class=\"hidden min-[1500px]:block\"></mn-lib-datetime>\n</div>\n" }]
3132
- }], propDecorators: { selectedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedDate", required: false }] }], todayLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "todayLabel", required: false }] }], pickDateLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pickDateLabel", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dateSelected: [{ type: i0.Output, args: ["dateSelected"] }] } });
3342
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'block' }, template: "<!-- Date selector bar. Layout is driven by the bar's own measured width, so it\n rearranges for the space the bar was given, not for the viewport. It never\n stacks: as room runs out it shows fewer days, then hands over to the picker.\n Nothing here is boxed \u2014 spacing does the grouping, not borders. -->\n<div class=\"flex items-center gap-4 px-4 py-3\">\n\n <!-- Today: returns the strip and the selection to the current day, and the\n control people reach for most, so it carries the weight of a filled button. -->\n <button (click)=\"goToToday()\"\n [data]=\"{ variant: 'fill', color: 'primary', size: 'md' }\"\n class=\"shrink-0\"\n mnButton\n type=\"button\">\n {{ todayLabel() }}\n </button>\n\n <!-- Too narrow for a usable strip, so the picker takes its place beside Today.\n Every date stays reachable on a phone, in one tap rather than by paging.\n The caption comes along, so the compact bar is labelled like the wide one \u2014\n it names the selected day's month rather than a visible range. -->\n @if (!showDayStrip()) {\n <span aria-hidden=\"true\"\n class=\"shrink-0 text-xs font-semibold tracking-wide whitespace-nowrap uppercase opacity-55\">\n {{ monthCaption() }}\n </span>\n\n <mn-lib-datetime (ngModelChange)=\"onDateModelChanged($event)\"\n [ngModel]=\"selectedDateString()\"\n [props]=\"pickerProps()\"\n class=\"block min-w-0 flex-1\"></mn-lib-datetime>\n }\n\n <!-- The strip takes the room left over and centres its contents in it, so on a\n wide bar the days sit in the middle rather than trailing off to one side. -->\n @if (showDayStrip()) {\n <div [attr.aria-label]=\"dayStripLabel()\"\n class=\"flex min-w-0 flex-1 items-center justify-center gap-3\"\n role=\"group\">\n <!-- Names the month the strip is in, so the day numbers are never loose. -->\n <span aria-hidden=\"true\"\n class=\"shrink-0 text-xs font-semibold tracking-wide whitespace-nowrap uppercase opacity-55\">\n {{ monthCaption() }}\n </span>\n\n <button (click)=\"navigatePrevious()\"\n [attr.aria-label]=\"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 <!-- Days read on one line so each sits at the same height as the buttons\n either side of it. Roving tabindex: one day is in the tab order, the\n arrow keys move between the rest and page the strip at either end. -->\n <div class=\"flex min-w-0 gap-1\">\n @for (tile of dayTiles(); track tile.date.getTime(); let i = $index) {\n <!-- A month break is marked by extra breathing room rather than a rule,\n so the run of days stays unbroken; the caption names both months. -->\n <button #tileButton\n (click)=\"selectDate(tile.date)\"\n (focus)=\"onTileFocus(i)\"\n (keydown)=\"onTileKeydown($event, i)\"\n [attr.aria-current]=\"tile.isToday ? 'date' : null\"\n [attr.aria-label]=\"tile.accessibleLabel\"\n [attr.aria-pressed]=\"tile.isSelected\"\n [attr.tabindex]=\"isTabbable(i) ? 0 : -1\"\n [class.bg-primary]=\"tile.isSelected\"\n [class.hover:bg-base-200]=\"!tile.isSelected\"\n [class.ml-3]=\"tile.startsNewMonth\"\n [class.text-primary-content]=\"tile.isSelected\"\n class=\"flex min-w-16 shrink-0 cursor-pointer items-center justify-center gap-1.5 rounded-lg px-3 py-2 text-sm transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary motion-reduce:transition-none\"\n type=\"button\">\n <span [class.opacity-60]=\"!tile.isSelected\"\n [class.opacity-90]=\"tile.isSelected\"\n aria-hidden=\"true\"\n class=\"text-xs font-medium uppercase\">\n {{ tile.dayName }}\n </span>\n <span aria-hidden=\"true\" class=\"font-bold tabular-nums\">{{ tile.dayNumber }}</span>\n <!-- Today's dot. Every day reserves the space so no tile changes\n width; only its colour says which one is today, and exactly one\n colour class applies at a time so they can't fight. -->\n <span [class.bg-primary-content]=\"tile.isToday && tile.isSelected\"\n [class.bg-primary]=\"tile.isToday && !tile.isSelected\"\n aria-hidden=\"true\"\n class=\"size-1 shrink-0 rounded-full\"></span>\n </button>\n }\n </div>\n\n <button (click)=\"navigateNext()\"\n [attr.aria-label]=\"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" }]
3343
+ }], propDecorators: { selectedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedDate", required: false }] }], todayLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "todayLabel", required: false }] }], pickDateLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pickDateLabel", required: false }] }], previousLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousLabel", required: false }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], dayStripLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayStripLabel", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dateSelected: [{ type: i0.Output, args: ["dateSelected"] }], tileButtons: [{ type: i0.ViewChildren, args: ['tileButton', { isSignal: true }] }] } });
3133
3344
 
3134
3345
  const mnMultiSelectVariants = tv({
3135
3346
  base: 'bg-base-100 border-1 border-base-300 text-base-content text-sm cursor-pointer',
@@ -5060,7 +5271,7 @@ class MnCollectionBase {
5060
5271
  this.cdr.markForCheck();
5061
5272
  });
5062
5273
  this.searchSubscription = this.searchSubject
5063
- .pipe(debounceTime$1(300))
5274
+ .pipe(debounceTime(300))
5064
5275
  .subscribe(value => {
5065
5276
  this.searchValue = value;
5066
5277
  this.applyFilter(true);
@@ -9017,7 +9228,7 @@ class CalendarViewComponent {
9017
9228
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9018
9229
  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: [
9019
9230
  provideMnCalendarConfig(DEFAULT_CALENDAR_CONFIG),
9020
- ], ngImport: i0, template: "<div class=\"w-full h-full flex flex-col\" role=\"application\" aria-label=\"Calendar\">\n\n <!-- Toolbar: date strip and calendar controls (view switcher + action buttons) share one row only on very\n wide screens (\u22651500px). The breakpoint matches the date-selector bar's own two-layer\u2192single-row switch\n so they flip in lockstep; a mismatched breakpoint left the controls inlined while the date bar was\n still stacked, producing a mixed layout. -->\n <div class=\"flex flex-col min-[1500px]:flex-row min-[1500px]:items-center gap-2 py-3\">\n <!-- Responsive date-selector bar: Today, day-window arrows, day tiles and a date picker. -->\n <mn-date-selector-bar\n (dateSelected)=\"onDateStripChange($event)\"\n [locale]=\"config.locale\"\n [pickDateLabel]=\"config.pickDateLabel\"\n [selectedDate]=\"focusDay\"\n [todayLabel]=\"config.todayLabel\"\n class=\"min-w-0 min-[1500px]:flex-1\">\n </mn-date-selector-bar>\n\n @if (!isMobileView || buttons.length || showButton) {\n <!-- order-first stacks the controls above the date strip when the toolbar wraps (below 1500px). -->\n <div class=\"flex items-center justify-end gap-2 shrink-0 order-first min-[1500px]:order-none\">\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=\"flex border border-base-300 rounded-md overflow-hidden\" role=\"tablist\">\n @for (view of viewOptions; track view.value) {\n <button\n (click)=\"switchView(view.value)\"\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 {{ view.label }}\n </button>\n }\n </div>\n }\n\n <!-- Action buttons (custom buttons + optional showButton CTA). -->\n @if (buttons.length || showButton) {\n <div [class.flex-1]=\"isMobileView\" class=\"flex items-center gap-2\">\n @for (btn of buttons; track btn.label) {\n <button (click)=\"btn.onClick()\" [class.flex-1]=\"isMobileView\" [data]=\"btn.buttonData || {}\"\n mnButton>{{ btn.label }}\n </button>\n }\n @if (showButton) {\n <button (click)=\"ButtonClickedEvent.emit()\" [class.flex-1]=\"isMobileView\" [data]=\"{}\"\n mnButton>{{ buttonTitle }}\n </button>\n }\n </div>\n }\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 class=\"min-w-0 min-h-0 overflow-hidden overflow-y-auto\">\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: "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: MnDateSelectorBar, selector: "mn-date-selector-bar", inputs: ["selectedDate", "todayLabel", "pickDateLabel", "locale"], outputs: ["dateSelected"] }] });
9231
+ ], ngImport: i0, template: "<div class=\"w-full h-full flex flex-col\" role=\"application\" aria-label=\"Calendar\">\n\n <!-- Toolbar: date strip and calendar controls (view switcher + action buttons) share one row only on very\n wide screens (\u22651500px). This breakpoint is the toolbar's own concern \u2014 the date-selector bar now sizes\n itself from its measured width, so it adapts to whatever column this layout hands it and the two no\n longer have to be kept in sync. -->\n <div class=\"flex flex-col min-[1500px]:flex-row min-[1500px]:items-center gap-2 py-3\">\n <!-- Responsive date-selector bar: Today, day-window arrows, day tiles and a date picker. -->\n <mn-date-selector-bar\n (dateSelected)=\"onDateStripChange($event)\"\n [locale]=\"config.locale\"\n [pickDateLabel]=\"config.pickDateLabel\"\n [selectedDate]=\"focusDay\"\n [todayLabel]=\"config.todayLabel\"\n class=\"min-w-0 min-[1500px]:flex-1\">\n </mn-date-selector-bar>\n\n @if (!isMobileView || buttons.length || showButton) {\n <!-- order-first stacks the controls above the date strip when the toolbar wraps (below 1500px). -->\n <div class=\"flex items-center justify-end gap-2 shrink-0 order-first min-[1500px]:order-none\">\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=\"flex border border-base-300 rounded-md overflow-hidden\" role=\"tablist\">\n @for (view of viewOptions; track view.value) {\n <button\n (click)=\"switchView(view.value)\"\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 {{ view.label }}\n </button>\n }\n </div>\n }\n\n <!-- Action buttons (custom buttons + optional showButton CTA). -->\n @if (buttons.length || showButton) {\n <div [class.flex-1]=\"isMobileView\" class=\"flex items-center gap-2\">\n @for (btn of buttons; track btn.label) {\n <button (click)=\"btn.onClick()\" [class.flex-1]=\"isMobileView\" [data]=\"btn.buttonData || {}\"\n mnButton>{{ btn.label }}\n </button>\n }\n @if (showButton) {\n <button (click)=\"ButtonClickedEvent.emit()\" [class.flex-1]=\"isMobileView\" [data]=\"{}\"\n mnButton>{{ buttonTitle }}\n </button>\n }\n </div>\n }\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 class=\"min-w-0 min-h-0 overflow-hidden overflow-y-auto\">\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: "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: MnDateSelectorBar, selector: "mn-date-selector-bar", inputs: ["selectedDate", "todayLabel", "pickDateLabel", "previousLabel", "nextLabel", "dayStripLabel", "locale"], outputs: ["dateSelected"] }] });
9021
9232
  }
9022
9233
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CalendarViewComponent, decorators: [{
9023
9234
  type: Component,
@@ -9031,7 +9242,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
9031
9242
  MnDateSelectorBar
9032
9243
  ], providers: [
9033
9244
  provideMnCalendarConfig(DEFAULT_CALENDAR_CONFIG),
9034
- ], template: "<div class=\"w-full h-full flex flex-col\" role=\"application\" aria-label=\"Calendar\">\n\n <!-- Toolbar: date strip and calendar controls (view switcher + action buttons) share one row only on very\n wide screens (\u22651500px). The breakpoint matches the date-selector bar's own two-layer\u2192single-row switch\n so they flip in lockstep; a mismatched breakpoint left the controls inlined while the date bar was\n still stacked, producing a mixed layout. -->\n <div class=\"flex flex-col min-[1500px]:flex-row min-[1500px]:items-center gap-2 py-3\">\n <!-- Responsive date-selector bar: Today, day-window arrows, day tiles and a date picker. -->\n <mn-date-selector-bar\n (dateSelected)=\"onDateStripChange($event)\"\n [locale]=\"config.locale\"\n [pickDateLabel]=\"config.pickDateLabel\"\n [selectedDate]=\"focusDay\"\n [todayLabel]=\"config.todayLabel\"\n class=\"min-w-0 min-[1500px]:flex-1\">\n </mn-date-selector-bar>\n\n @if (!isMobileView || buttons.length || showButton) {\n <!-- order-first stacks the controls above the date strip when the toolbar wraps (below 1500px). -->\n <div class=\"flex items-center justify-end gap-2 shrink-0 order-first min-[1500px]:order-none\">\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=\"flex border border-base-300 rounded-md overflow-hidden\" role=\"tablist\">\n @for (view of viewOptions; track view.value) {\n <button\n (click)=\"switchView(view.value)\"\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 {{ view.label }}\n </button>\n }\n </div>\n }\n\n <!-- Action buttons (custom buttons + optional showButton CTA). -->\n @if (buttons.length || showButton) {\n <div [class.flex-1]=\"isMobileView\" class=\"flex items-center gap-2\">\n @for (btn of buttons; track btn.label) {\n <button (click)=\"btn.onClick()\" [class.flex-1]=\"isMobileView\" [data]=\"btn.buttonData || {}\"\n mnButton>{{ btn.label }}\n </button>\n }\n @if (showButton) {\n <button (click)=\"ButtonClickedEvent.emit()\" [class.flex-1]=\"isMobileView\" [data]=\"{}\"\n mnButton>{{ buttonTitle }}\n </button>\n }\n </div>\n }\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 class=\"min-w-0 min-h-0 overflow-hidden overflow-y-auto\">\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"] }]
9245
+ ], template: "<div class=\"w-full h-full flex flex-col\" role=\"application\" aria-label=\"Calendar\">\n\n <!-- Toolbar: date strip and calendar controls (view switcher + action buttons) share one row only on very\n wide screens (\u22651500px). This breakpoint is the toolbar's own concern \u2014 the date-selector bar now sizes\n itself from its measured width, so it adapts to whatever column this layout hands it and the two no\n longer have to be kept in sync. -->\n <div class=\"flex flex-col min-[1500px]:flex-row min-[1500px]:items-center gap-2 py-3\">\n <!-- Responsive date-selector bar: Today, day-window arrows, day tiles and a date picker. -->\n <mn-date-selector-bar\n (dateSelected)=\"onDateStripChange($event)\"\n [locale]=\"config.locale\"\n [pickDateLabel]=\"config.pickDateLabel\"\n [selectedDate]=\"focusDay\"\n [todayLabel]=\"config.todayLabel\"\n class=\"min-w-0 min-[1500px]:flex-1\">\n </mn-date-selector-bar>\n\n @if (!isMobileView || buttons.length || showButton) {\n <!-- order-first stacks the controls above the date strip when the toolbar wraps (below 1500px). -->\n <div class=\"flex items-center justify-end gap-2 shrink-0 order-first min-[1500px]:order-none\">\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=\"flex border border-base-300 rounded-md overflow-hidden\" role=\"tablist\">\n @for (view of viewOptions; track view.value) {\n <button\n (click)=\"switchView(view.value)\"\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 {{ view.label }}\n </button>\n }\n </div>\n }\n\n <!-- Action buttons (custom buttons + optional showButton CTA). -->\n @if (buttons.length || showButton) {\n <div [class.flex-1]=\"isMobileView\" class=\"flex items-center gap-2\">\n @for (btn of buttons; track btn.label) {\n <button (click)=\"btn.onClick()\" [class.flex-1]=\"isMobileView\" [data]=\"btn.buttonData || {}\"\n mnButton>{{ btn.label }}\n </button>\n }\n @if (showButton) {\n <button (click)=\"ButtonClickedEvent.emit()\" [class.flex-1]=\"isMobileView\" [data]=\"{}\"\n mnButton>{{ buttonTitle }}\n </button>\n }\n </div>\n }\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 class=\"min-w-0 min-h-0 overflow-hidden overflow-y-auto\">\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"] }]
9035
9246
  }], ctorParameters: () => [], propDecorators: { showButton: [{
9036
9247
  type: Input
9037
9248
  }], buttonTitle: [{