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.
- package/fesm2022/mn-angular-lib.mjs +309 -98
- package/fesm2022/mn-angular-lib.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mn-angular-lib.d.ts +140 -44
|
@@ -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,
|
|
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
|
-
*
|
|
2921
|
-
*
|
|
2922
|
-
*
|
|
2923
|
-
*
|
|
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
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
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
|
|
2946
|
-
* tiles
|
|
2947
|
-
*
|
|
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
|
-
*
|
|
2953
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
2988
|
-
|
|
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
|
-
|
|
2996
|
-
|
|
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
|
-
*
|
|
3009
|
-
*
|
|
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
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
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
|
-
/**
|
|
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(
|
|
3031
|
-
date.setDate(
|
|
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
|
-
|
|
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
|
-
/**
|
|
3158
|
+
/** Shows the days before the visible ones, without changing the selection. */
|
|
3056
3159
|
navigatePrevious() {
|
|
3057
|
-
this.
|
|
3160
|
+
this.shiftWindow(-1);
|
|
3058
3161
|
}
|
|
3059
|
-
/**
|
|
3162
|
+
/** Shows the days after the visible ones, without changing the selection. */
|
|
3060
3163
|
navigateNext() {
|
|
3061
|
-
this.
|
|
3164
|
+
this.shiftWindow(1);
|
|
3062
3165
|
}
|
|
3063
|
-
/**
|
|
3166
|
+
/** Returns to today: brings today into the strip and selects it. */
|
|
3064
3167
|
goToToday() {
|
|
3065
|
-
this.
|
|
3066
|
-
//
|
|
3067
|
-
//
|
|
3068
|
-
|
|
3069
|
-
|
|
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:
|
|
3082
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
3102
|
-
*
|
|
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
|
-
|
|
3105
|
-
|
|
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
|
|
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
|
|
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
|
|
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).
|
|
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).
|
|
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: [{
|