Skip to content

Commit 9635454

Browse files
committed
fix: Incorrect handling of allowed-dates in month-picker and year-picker modes (fixes #1035, fixes #1047)
1 parent 8710a63 commit 9635454

File tree

11 files changed

+87
-51
lines changed

11 files changed

+87
-51
lines changed

src/VueDatePicker/components/MonthPicker/month-picker.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { checkMinMaxValue, getMonths, groupListAndMap } from '@/utils/util';
55
import {
66
checkHighlightMonth,
77
getDate,
8-
getDisabledMonths,
98
getMaxMonth,
109
getMinMonth,
1110
isDateBetween,
1211
isMonthAllowed,
12+
isMonthDisabled,
1313
resetDate,
1414
setDateMonthOrYear,
1515
} from '@/utils/date-utils';
@@ -136,7 +136,7 @@ export const useMonthPicker = (props: PickerBasePropsType, emit: VueEmit) => {
136136
getMinMonth(year.value(instance), propDates.value.minDate),
137137
getMaxMonth(year.value(instance), propDates.value.maxDate),
138138
) ||
139-
getDisabledMonths(propDates.value.disabledDates, year.value(instance)).includes(month.value) ||
139+
isMonthDisabled(propDates.value.disabledDates, year.value(instance), month.value) ||
140140
defaultedFilters.value.months?.includes(month.value) ||
141141
!isMonthAllowed(propDates.value.allowedDates, year.value(instance), month.value);
142142
const isBetween = isMonthBetween(month.value, instance);

src/VueDatePicker/components/QuarterPicker/quarter-picker.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export const useQuarterPicker = (props: PickerBasePropsType, emit: VueEmit) => {
100100
value: start,
101101
active: isQuarterActive.value(start),
102102
highlighted,
103-
disabled: disabled,
103+
disabled,
104104
isBetween,
105105
};
106106
});

src/VueDatePicker/components/YearPicker/year-picker.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,20 @@ export const useYearPicker = (props: PickerBasePropsType, emit: VueEmit) => {
5050
return false;
5151
};
5252

53+
const isYearAllowed = (year: number) => {
54+
if (propDates.value.allowedDates instanceof Map) {
55+
return propDates.value.allowedDates.size ? propDates.value.allowedDates.has(`${year}`) : false;
56+
}
57+
return true;
58+
};
59+
60+
const isYearDisabled = (year: number) => {
61+
if (propDates.value.disabledDates instanceof Map) {
62+
return propDates.value.disabledDates.size ? propDates.value.disabledDates.has(`${year}`) : false;
63+
}
64+
return true;
65+
};
66+
5367
const groupedYears = computed(() => {
5468
return groupListAndMap(getYears(props.yearRange, props.locale, props.reverseYears), (year: IDefaultSelect) => {
5569
const active = isYearActive(year.value);
@@ -58,7 +72,10 @@ export const useYearPicker = (props: PickerBasePropsType, emit: VueEmit) => {
5872
year.value,
5973
getMinMaxYear(propDates.value.minDate),
6074
getMinMaxYear(propDates.value.maxDate),
61-
) || defaultedFilters.value.years.includes(year.value);
75+
) ||
76+
defaultedFilters.value.years.includes(year.value) ||
77+
!isYearAllowed(year.value) ||
78+
isYearDisabled(year.value);
6279
const isBetween = isYearBetween(year.value) && !active;
6380
const highlighted = checkHighlightYear(defaultedHighlight.value, year.value);
6481
return { active, disabled, isBetween, highlighted };

src/VueDatePicker/composables/defaults.ts

+2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ export const useDefaults = (props: AllPropsType | PickerBasePropsType) => {
9292
markers: props.markers,
9393
timezone: defaultedTz.value,
9494
isSpecific: props.monthPicker || props.yearPicker || props.quarterPicker,
95+
isMonthPicker: props.monthPicker,
96+
isYearPicker: props.yearPicker,
9597
}),
9698
);
9799

src/VueDatePicker/constants/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,9 @@ export enum EventKey {
4747
pageUp = 'PageUp',
4848
pageDown = 'PageDown',
4949
}
50+
51+
export enum MAP_KEY_FORMAT {
52+
MONTH_AND_YEAR = 'MM-yyyy',
53+
YEAR = 'yyyy',
54+
DATE = 'dd-MM-yyyy',
55+
}

src/VueDatePicker/interfaces.ts

+2
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ export interface MapPropDatesOpts {
381381
markers: IMarker[];
382382
timezone: TimeZoneConfig | undefined;
383383
isSpecific?: boolean;
384+
isMonthPicker: boolean;
385+
isYearPicker: boolean;
384386
}
385387

386388
export type CustomClass = string | string[];

src/VueDatePicker/utils/date-utils.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import type {
4242
} from '@/interfaces';
4343

4444
import type { Duration, Locale } from 'date-fns';
45+
import { padZero } from '../../../tests/utils.ts';
4546

4647
const parseTextToDate = (
4748
value: string,
@@ -403,28 +404,26 @@ export const checkTimeMinMax = (
403404
// Returns a getDate object with a set of time from a provided date
404405
export const setTimeValue = (date: Date): Date => set(getDate(), getTimeObj(date));
405406

406-
export const getDisabledMonths = (
407+
export const isMonthDisabled = (
407408
disabledDates: Map<string, Date | null> | null | ((date: Date) => boolean),
408409
year: number,
410+
month: number,
409411
) => {
410412
if (disabledDates instanceof Map) {
411-
return Array.from(disabledDates.values())
412-
.filter((date) => getYear(getDate(date)) === year)
413-
.map((date) => getMonth(date as Date));
413+
const key = `${padZero(month + 1)}-${year}`;
414+
return disabledDates.size ? disabledDates.has(key) : false;
414415
}
415-
return [];
416+
return false;
416417
};
417418

418419
export const isMonthAllowed = (
419-
disabledDates: Map<string, Date | null> | null | ((date: Date) => boolean),
420+
allowedDates: Map<string, Date | null> | null | ((date: Date) => boolean),
420421
year: number,
421422
month: number,
422423
) => {
423-
if (disabledDates instanceof Map) {
424-
const months = Array.from(disabledDates.values())
425-
.filter((date) => getYear(getDate(date)) === year)
426-
.map((date) => getMonth(date as Date));
427-
return months.length ? months.includes(month) : true;
424+
if (allowedDates instanceof Map) {
425+
const key = `${padZero(month + 1)}-${year}`;
426+
return allowedDates.size ? allowedDates.has(key) : true;
428427
}
429428
return true;
430429
};

src/VueDatePicker/utils/defaults.ts

+30-27
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,38 @@
11
import type {
2-
AriaLabels,
3-
IFormat,
4-
Transition,
5-
TextInputOptions,
6-
DateFilter,
72
ActionRowData,
8-
MultiCalendarsProp,
9-
MultiCalendarsOptions,
10-
OptionEnabled,
11-
TextInputProp,
12-
InlineProp,
13-
InlineOptions,
3+
AriaLabels,
144
Config,
15-
HighlightProp,
5+
DateFilter,
166
Highlight,
177
HighlightFn,
18-
WeekNumbersProp,
19-
WeekNumbersOpts,
20-
RangeProp,
21-
RangeConfig,
22-
TimeZoneProp,
23-
TimeZoneConfig,
8+
HighlightProp,
9+
IFormat,
2410
IMarker,
25-
PropDates,
26-
MultiDatesProp,
27-
MultiDatesDefault,
11+
InlineOptions,
12+
InlineProp,
2813
MapPropDatesOpts,
14+
MultiCalendarsOptions,
15+
MultiCalendarsProp,
16+
MultiDatesDefault,
17+
MultiDatesProp,
18+
OptionEnabled,
19+
PropDates,
20+
RangeConfig,
21+
RangeProp,
22+
TextInputOptions,
23+
TextInputProp,
24+
TimeZoneConfig,
25+
TimeZoneProp,
26+
Transition,
2927
UIOpts,
3028
UIParsed,
29+
WeekNumbersOpts,
30+
WeekNumbersProp,
3131
} from '@/interfaces';
3232
import { getDate } from '@/utils/date-utils';
3333
import { dateToTimezoneSafe, sanitizeDateToLocal } from '@/utils/timezone';
34-
import { getMapKey, shouldMap } from '@/utils/util';
34+
import { getMapKey, getMapKeyType, shouldMap } from '@/utils/util';
35+
import { MAP_KEY_FORMAT } from '@/constants';
3536

3637
export const mergeDefaultTransitions = (conf: Partial<Transition>): Transition => ({
3738
menuAppearTop: 'dp-menu-appear-top',
@@ -254,12 +255,13 @@ export const getDefaultTimeZone = (timeZone: TimeZoneProp) => {
254255
const datesArrToMap = (
255256
datesArr: (Date | string | number)[],
256257
timezone: TimeZoneConfig | undefined,
258+
format: MAP_KEY_FORMAT,
257259
reset?: boolean,
258260
): Map<string, Date | null> => {
259261
return new Map(
260262
datesArr.map((date) => {
261263
const d = dateToTimezoneSafe(date, timezone, reset);
262-
return [getMapKey(d), d];
264+
return [getMapKey(d, format), d];
263265
}),
264266
);
265267
};
@@ -269,7 +271,7 @@ const mapMarkers = (markers: IMarker[], timezone: TimeZoneConfig | undefined) =>
269271
return new Map(
270272
markers.map((marker) => {
271273
const date = dateToTimezoneSafe(marker.date, timezone);
272-
return [getMapKey(date), marker];
274+
return [getMapKey(date, MAP_KEY_FORMAT.DATE), marker];
273275
}),
274276
);
275277
}
@@ -281,18 +283,19 @@ const mapMarkers = (markers: IMarker[], timezone: TimeZoneConfig | undefined) =>
281283
* All validation that is done from these props will now be in sync with provided timezone config
282284
*/
283285
export const mapPropDates = (opts: MapPropDatesOpts): PropDates => {
286+
const format = getMapKeyType(opts.isMonthPicker, opts.isYearPicker);
284287
return {
285288
minDate: sanitizeDateToLocal(opts.minDate, opts.timezone, opts.isSpecific),
286289
maxDate: sanitizeDateToLocal(opts.maxDate, opts.timezone, opts.isSpecific),
287290
disabledDates: shouldMap(opts.disabledDates)
288-
? datesArrToMap(opts.disabledDates, opts.timezone, opts.isSpecific)
291+
? datesArrToMap(opts.disabledDates, opts.timezone, format, opts.isSpecific)
289292
: opts.disabledDates,
290293
allowedDates: shouldMap(opts.allowedDates)
291-
? datesArrToMap(opts.allowedDates, opts.timezone, opts.isSpecific)
294+
? datesArrToMap(opts.allowedDates, opts.timezone, format, opts.isSpecific)
292295
: null,
293296
highlight:
294297
typeof opts.highlight === 'object' && shouldMap(opts.highlight?.dates)
295-
? datesArrToMap(opts.highlight.dates, opts.timezone)
298+
? datesArrToMap(opts.highlight.dates, opts.timezone, format)
296299
: (opts.highlight as HighlightFn),
297300
markers: mapMarkers(opts.markers, opts.timezone),
298301
};

src/VueDatePicker/utils/util.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import type { ComponentPublicInstance } from 'vue';
12
import { unref } from 'vue';
2-
import { format } from 'date-fns';
3+
import { format, type Locale } from 'date-fns';
34

45
import type {
56
Config,
@@ -10,11 +11,9 @@ import type {
1011
ModelValue,
1112
OverlayGridItem,
1213
} from '@/interfaces';
13-
import { type Locale } from 'date-fns';
14-
import type { ComponentPublicInstance } from 'vue';
1514
import { getDate } from '@/utils/date-utils';
1615
import { localToTz } from '@/utils/timezone';
17-
import { EventKey } from '@/constants';
16+
import { EventKey, MAP_KEY_FORMAT } from '@/constants';
1817

1918
export const getArrayInArray = <T>(list: T[], increment = 3): T[][] => {
2019
const items = [];
@@ -280,16 +279,16 @@ export const formatNumber = (num: number, locale: string): string => {
280279
return new Intl.NumberFormat(locale, { useGrouping: false, style: 'decimal' }).format(num);
281280
};
282281

283-
export const getMapKey = (date: Date | string | number) => {
284-
return format(date, 'dd-MM-yyyy');
282+
export const getMapKey = (date: Date | string | number, mapKeyFormat?: MAP_KEY_FORMAT) => {
283+
return format(date, mapKeyFormat ?? MAP_KEY_FORMAT.DATE);
285284
};
286285

287286
export const shouldMap = (arr: any): arr is Date[] | string[] | boolean => {
288287
return Array.isArray(arr);
289288
};
290289

291-
export const getMapDate = <T>(date: Date, map: Map<string, T>): T | undefined => {
292-
return map.get(getMapKey(date));
290+
export const getMapDate = <T>(date: Date, map: Map<string, T>, format?: MAP_KEY_FORMAT): T | undefined => {
291+
return map.get(getMapKey(date, format));
293292
};
294293

295294
export const matchDate = (date: Date, mapOrFn: Map<string, any> | ((date: Date) => boolean) | null) => {
@@ -319,3 +318,9 @@ export const isIOS = () => {
319318
(navigator.userAgent.includes('Mac') && 'ontouchend' in document)
320319
);
321320
};
321+
322+
export const getMapKeyType = (monthPicker: boolean, yearPicker: boolean): MAP_KEY_FORMAT => {
323+
if (monthPicker) return MAP_KEY_FORMAT.MONTH_AND_YEAR;
324+
if (yearPicker) return MAP_KEY_FORMAT.YEAR;
325+
return MAP_KEY_FORMAT.DATE;
326+
};

tests/unit/components/MonthYearPicker.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ describe('Month and Year picker components', () => {
157157
it('Should disable months based on disabled dates', async () => {
158158
const currentMonth = getMonth(new Date());
159159
const wrapper = mount(MonthPicker, {
160-
props: { ...props, disabledDates: [new Date()] },
160+
props: { ...props, disabledDates: [new Date()], monthPicker: true },
161161
}) as unknown as MonthPickerCmp<{ groupedMonths: (i: number) => OverlayGridItem[][] }>;
162162

163163
const monthValues = wrapper.vm.groupedMonths(0);

tests/unit/utils.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ const getMapDatesOpts = (date: Date, timezone: TimeZoneConfig, highlightFn: (dat
8282
markers: [],
8383
highlight: highlightFn,
8484
timezone,
85+
isMonthPicker: false,
86+
isYearPicker: false,
8587
};
8688
};
8789

0 commit comments

Comments
 (0)