@rupe/v-datepicker 1.0.0-beta.0 → 1.0.0-beta.3
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/README.md +48 -3
- package/dist/components/Calendar/CalendarRoot.vue.d.ts +6 -0
- package/dist/components/Calendar/useCalendar.d.ts +2 -0
- package/dist/components/DatePicker/DatePickerRoot.vue.d.ts +5 -1
- package/dist/index.cjs +99 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +99 -60
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Calendar/CalendarMonthYearOverlay.vue +0 -1
- package/src/components/Calendar/CalendarOverlayItem.vue +84 -41
- package/src/components/Calendar/CalendarRoot.vue +10 -0
- package/src/components/Calendar/useCalendar.ts +21 -32
- package/src/components/DatePicker/DatePickerCalendar.vue +2 -0
- package/src/components/DatePicker/DatePickerRoot.vue +10 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from "vue";
|
|
2
|
+
import { computed, onMounted, nextTick } from "vue";
|
|
3
3
|
import type { DateValue } from "@internationalized/date";
|
|
4
|
-
import { Primitive } from "../Primitive";
|
|
4
|
+
import { Primitive, usePrimitiveElement } from "../Primitive";
|
|
5
5
|
import type { PrimitiveProps } from "../Primitive";
|
|
6
6
|
import { injectCalendarRootContext } from "./CalendarRoot.vue";
|
|
7
7
|
import { useKbd } from "../../shared";
|
|
@@ -20,29 +20,55 @@ const props = withDefaults(defineProps<CalendarOverlayItemProps>(), {
|
|
|
20
20
|
|
|
21
21
|
const rootContext = injectCalendarRootContext();
|
|
22
22
|
const years = computed(() => rootContext.years.value);
|
|
23
|
-
const maxValue = computed(() =>
|
|
24
|
-
props.type === "month" ? 12 : years.value.length,
|
|
25
|
-
);
|
|
26
23
|
const overlayContext = injectCalendarMonthYearOverlayContext();
|
|
27
24
|
const dataValue = computed(() => `${props.type}-${props.date[props.type]}`);
|
|
28
25
|
|
|
26
|
+
const { primitiveElement, currentElement } = usePrimitiveElement();
|
|
27
|
+
|
|
29
28
|
const isFocusedDate = computed(() => {
|
|
30
|
-
|
|
29
|
+
const selected = rootContext.modelValue.value;
|
|
30
|
+
const selectedDate = Array.isArray(selected)
|
|
31
|
+
? selected[selected.length - 1]
|
|
32
|
+
: selected;
|
|
33
|
+
|
|
34
|
+
if (selectedDate) {
|
|
35
|
+
if (props.type === "month") return selectedDate.month === props.date.month;
|
|
36
|
+
return selectedDate.year === props.date.year;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (props.type === "month") {
|
|
31
40
|
return rootContext.currentMonth.value === props.date.monthName;
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
return rootContext.currentYear.value === props.date.year.toString();
|
|
33
44
|
});
|
|
34
45
|
|
|
46
|
+
const ariaLabel = computed(() => {
|
|
47
|
+
if (props.type === "month") return props.date.monthName;
|
|
48
|
+
return props.date.year.toString();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
onMounted(() => {
|
|
52
|
+
if (isFocusedDate.value) {
|
|
53
|
+
nextTick(() => {
|
|
54
|
+
currentElement.value?.focus();
|
|
55
|
+
currentElement.value?.scrollIntoView({ block: "nearest" });
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function isDateSelectable(date: DateValue) {
|
|
61
|
+
return (
|
|
62
|
+
!rootContext.isDateDisabled(date) && !rootContext.isDateUnavailable?.(date)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
35
66
|
function closeOverlay() {
|
|
36
67
|
rootContext.monthYearOverlayState.value = false;
|
|
37
68
|
}
|
|
38
69
|
|
|
39
70
|
function handleClick() {
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
rootContext.isDateDisabled(props.date) ||
|
|
43
|
-
rootContext.isDateUnavailable?.(props.date)
|
|
44
|
-
)
|
|
45
|
-
return;
|
|
71
|
+
if (!isDateSelectable(props.date)) return;
|
|
46
72
|
|
|
47
73
|
rootContext.onDateChange(props.date);
|
|
48
74
|
closeOverlay();
|
|
@@ -50,52 +76,68 @@ function handleClick() {
|
|
|
50
76
|
|
|
51
77
|
const kbd = useKbd();
|
|
52
78
|
|
|
53
|
-
function
|
|
79
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
54
80
|
if (props.disabled) return;
|
|
55
81
|
e.preventDefault();
|
|
56
82
|
e.stopPropagation();
|
|
57
83
|
const parentElement = rootContext.parentElement.value!;
|
|
58
84
|
|
|
59
|
-
const
|
|
85
|
+
const itemsPerRow = overlayContext.itemsPerRow.value;
|
|
60
86
|
const sign = rootContext.dir.value === "rtl" ? -1 : 1;
|
|
87
|
+
const currentValue = props.date[props.type];
|
|
88
|
+
|
|
89
|
+
function focusItem(value: number) {
|
|
90
|
+
const candidate = parentElement.querySelector<HTMLElement>(
|
|
91
|
+
`[data-value='${props.type}-${value}']`,
|
|
92
|
+
);
|
|
93
|
+
candidate?.focus();
|
|
94
|
+
candidate?.scrollIntoView({ block: "nearest" });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function shiftFocus(add: number) {
|
|
98
|
+
let nextValue = currentValue + add;
|
|
99
|
+
|
|
100
|
+
if (props.type === "month") {
|
|
101
|
+
nextValue = ((nextValue - 1 + 12) % 12) + 1;
|
|
102
|
+
} else {
|
|
103
|
+
const minYear = years.value[0]!.year;
|
|
104
|
+
const maxYear = years.value[years.value.length - 1]!.year;
|
|
105
|
+
nextValue = Math.max(minYear, Math.min(nextValue, maxYear));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
focusItem(nextValue);
|
|
109
|
+
}
|
|
61
110
|
|
|
62
|
-
// TODO: check how we can reuse code with CalendarCellTrigger
|
|
63
111
|
switch (e.code) {
|
|
64
112
|
case kbd.ARROW_RIGHT:
|
|
65
|
-
shiftFocus(
|
|
113
|
+
shiftFocus(sign);
|
|
66
114
|
break;
|
|
67
115
|
case kbd.ARROW_LEFT:
|
|
68
|
-
shiftFocus(
|
|
116
|
+
shiftFocus(-sign);
|
|
69
117
|
break;
|
|
70
118
|
case kbd.ARROW_UP:
|
|
71
|
-
shiftFocus(
|
|
119
|
+
shiftFocus(-itemsPerRow);
|
|
72
120
|
break;
|
|
73
121
|
case kbd.ARROW_DOWN:
|
|
74
|
-
shiftFocus(
|
|
122
|
+
shiftFocus(itemsPerRow);
|
|
123
|
+
break;
|
|
124
|
+
case kbd.HOME: {
|
|
125
|
+
const firstValue = props.type === "month" ? 1 : years.value[0]!.year;
|
|
126
|
+
focusItem(firstValue);
|
|
75
127
|
break;
|
|
76
|
-
case kbd.ENTER:
|
|
77
|
-
case kbd.SPACE_CODE:
|
|
78
|
-
rootContext.onDateChange(props.date);
|
|
79
|
-
closeOverlay();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function shiftFocus(date: DateValue, add: number) {
|
|
83
|
-
let nextDate = date[props.type] + add;
|
|
84
|
-
|
|
85
|
-
// TODO: fix for years
|
|
86
|
-
if (nextDate <= 0) {
|
|
87
|
-
nextDate = maxValue.value + nextDate;
|
|
88
128
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
129
|
+
case kbd.END: {
|
|
130
|
+
const lastValue =
|
|
131
|
+
props.type === "month" ? 12 : years.value[years.value.length - 1]!.year;
|
|
132
|
+
focusItem(lastValue);
|
|
133
|
+
break;
|
|
92
134
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
135
|
+
case kbd.ENTER:
|
|
136
|
+
case kbd.SPACE_CODE:
|
|
137
|
+
if (isDateSelectable(props.date)) {
|
|
138
|
+
rootContext.onDateChange(props.date);
|
|
139
|
+
closeOverlay();
|
|
140
|
+
}
|
|
99
141
|
}
|
|
100
142
|
}
|
|
101
143
|
</script>
|
|
@@ -106,9 +148,10 @@ function handleArrowKey(e: KeyboardEvent) {
|
|
|
106
148
|
@click="handleClick"
|
|
107
149
|
ref="primitiveElement"
|
|
108
150
|
role="button"
|
|
151
|
+
:aria-label="ariaLabel"
|
|
109
152
|
:aria-disabled="disabled"
|
|
110
153
|
:data-value="dataValue"
|
|
111
|
-
@keydown.up.down.left.right.space.enter="
|
|
154
|
+
@keydown.up.down.left.right.space.enter.home.end="handleKeydown"
|
|
112
155
|
@keydown.enter.prevent
|
|
113
156
|
:tabindex="isFocusedDate ? 0 : -1"
|
|
114
157
|
>
|
|
@@ -109,6 +109,10 @@ export interface CalendarRootProps extends PrimitiveProps {
|
|
|
109
109
|
multiple?: boolean;
|
|
110
110
|
/** Whether or not to disable days outside the current view. */
|
|
111
111
|
disableDaysOutsideCurrentView?: boolean;
|
|
112
|
+
/** The minimum year displayed in the year overlay */
|
|
113
|
+
minYear?: number;
|
|
114
|
+
/** The maximum year displayed in the year overlay */
|
|
115
|
+
maxYear?: number;
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
export type CalendarRootEmits = {
|
|
@@ -144,6 +148,8 @@ const props = withDefaults(defineProps<CalendarRootProps>(), {
|
|
|
144
148
|
isDateDisabled: undefined,
|
|
145
149
|
isDateUnavailable: undefined,
|
|
146
150
|
disableDaysOutsideCurrentView: false,
|
|
151
|
+
minYear: 1900,
|
|
152
|
+
maxYear: 2100,
|
|
147
153
|
});
|
|
148
154
|
const emits = defineEmits<CalendarRootEmits>();
|
|
149
155
|
defineSlots<{
|
|
@@ -187,6 +193,8 @@ const {
|
|
|
187
193
|
dir: propDir,
|
|
188
194
|
locale: propLocale,
|
|
189
195
|
disableDaysOutsideCurrentView,
|
|
196
|
+
minYear,
|
|
197
|
+
maxYear,
|
|
190
198
|
} = toRefs(props);
|
|
191
199
|
|
|
192
200
|
const { primitiveElement, currentElement: parentElement } =
|
|
@@ -249,6 +257,8 @@ const {
|
|
|
249
257
|
calendarLabel,
|
|
250
258
|
nextPage: propsNextPage,
|
|
251
259
|
prevPage: propsPrevPage,
|
|
260
|
+
minYear,
|
|
261
|
+
maxYear,
|
|
252
262
|
});
|
|
253
263
|
|
|
254
264
|
const { isInvalid, isDateSelected } = useCalendarState({
|
|
@@ -34,6 +34,8 @@ export type UseCalendarProps = {
|
|
|
34
34
|
calendarLabel: Ref<string | undefined>;
|
|
35
35
|
nextPage: Ref<((placeholder: DateValue) => DateValue) | undefined>;
|
|
36
36
|
prevPage: Ref<((placeholder: DateValue) => DateValue) | undefined>;
|
|
37
|
+
minYear: Ref<number>;
|
|
38
|
+
maxYear: Ref<number>;
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
export type UseCalendarStateProps = {
|
|
@@ -110,6 +112,10 @@ function handlePrevPage(
|
|
|
110
112
|
export function useCalendar(props: UseCalendarProps) {
|
|
111
113
|
const formatter = useDateFormatter(props.locale.value);
|
|
112
114
|
|
|
115
|
+
watch(props.locale, (newLocale) => {
|
|
116
|
+
formatter.setLocale(newLocale);
|
|
117
|
+
});
|
|
118
|
+
|
|
113
119
|
const headingFormatOptions = computed(() => {
|
|
114
120
|
const options: DateFormatterOptions = {
|
|
115
121
|
calendar: props.placeholder.value.calendar.identifier,
|
|
@@ -329,10 +335,7 @@ export function useCalendar(props: UseCalendarProps) {
|
|
|
329
335
|
);
|
|
330
336
|
|
|
331
337
|
const headingValue = computed(() => {
|
|
332
|
-
if (!grid.value.length) return "";
|
|
333
|
-
|
|
334
|
-
if (props.locale.value !== formatter.getLocale())
|
|
335
|
-
formatter.setLocale(props.locale.value);
|
|
338
|
+
if (!grid.value.length || !grid.value[0]) return "";
|
|
336
339
|
|
|
337
340
|
if (grid.value.length === 1) {
|
|
338
341
|
const month = grid.value[0].value;
|
|
@@ -340,7 +343,8 @@ export function useCalendar(props: UseCalendarProps) {
|
|
|
340
343
|
}
|
|
341
344
|
|
|
342
345
|
const startMonth = toDate(grid.value[0].value);
|
|
343
|
-
const
|
|
346
|
+
const lastMonth = grid.value[grid.value.length - 1];
|
|
347
|
+
const endMonth = toDate(lastMonth ? lastMonth.value : grid.value[0].value);
|
|
344
348
|
|
|
345
349
|
const startMonthName = formatter.fullMonth(
|
|
346
350
|
startMonth,
|
|
@@ -367,36 +371,22 @@ export function useCalendar(props: UseCalendarProps) {
|
|
|
367
371
|
return content;
|
|
368
372
|
});
|
|
369
373
|
|
|
370
|
-
|
|
371
|
-
const currentMonth = computed(() => {
|
|
374
|
+
function formatGridDate(fn: (date: Date) => string): string {
|
|
372
375
|
if (!grid.value.length) return "";
|
|
373
376
|
|
|
374
|
-
if (props.locale.value !== formatter.getLocale())
|
|
375
|
-
formatter.setLocale(props.locale.value);
|
|
376
|
-
|
|
377
377
|
const date = grid.value[0]?.value;
|
|
378
|
+
if (!date) return "";
|
|
378
379
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return "";
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
const currentYear = computed(() => {
|
|
387
|
-
if (!grid.value.length) return "";
|
|
388
|
-
|
|
389
|
-
if (props.locale.value !== formatter.getLocale())
|
|
390
|
-
formatter.setLocale(props.locale.value);
|
|
391
|
-
|
|
392
|
-
const date = grid.value[0]?.value;
|
|
380
|
+
return fn(toDate(date));
|
|
381
|
+
}
|
|
393
382
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
383
|
+
const currentMonth = computed(() =>
|
|
384
|
+
formatGridDate((d) => formatter.fullMonth(d, headingFormatOptions.value)),
|
|
385
|
+
);
|
|
397
386
|
|
|
398
|
-
|
|
399
|
-
|
|
387
|
+
const currentYear = computed(() =>
|
|
388
|
+
formatGridDate((d) => formatter.fullYear(d, headingFormatOptions.value)),
|
|
389
|
+
);
|
|
400
390
|
|
|
401
391
|
// TODO: look if there is a better way to create months with locale month name
|
|
402
392
|
const months = computed(() => {
|
|
@@ -417,10 +407,9 @@ export function useCalendar(props: UseCalendarProps) {
|
|
|
417
407
|
});
|
|
418
408
|
});
|
|
419
409
|
|
|
420
|
-
// TODO: add min and max for years and maybe we should switch to date object here as well for consistency
|
|
421
410
|
const years = computed(() => {
|
|
422
|
-
const startDate = props.placeholder.value.set({ year:
|
|
423
|
-
const endDate = props.placeholder.value.set({ year:
|
|
411
|
+
const startDate = props.placeholder.value.set({ year: props.minYear.value });
|
|
412
|
+
const endDate = props.placeholder.value.set({ year: props.maxYear.value });
|
|
424
413
|
|
|
425
414
|
return createYearRange({ start: startDate, end: endDate });
|
|
426
415
|
});
|
|
@@ -27,6 +27,8 @@ const rootContext = injectDatePickerRootContext();
|
|
|
27
27
|
readonly: rootContext.readonly.value,
|
|
28
28
|
preventDeselect: rootContext.preventDeselect.value,
|
|
29
29
|
dir: rootContext.dir.value,
|
|
30
|
+
minYear: rootContext.minYear.value,
|
|
31
|
+
maxYear: rootContext.maxYear.value,
|
|
30
32
|
}"
|
|
31
33
|
:model-value="rootContext.modelValue.value"
|
|
32
34
|
:placeholder="rootContext.placeholder.value"
|
|
@@ -44,6 +44,8 @@ type DatePickerRootContext = {
|
|
|
44
44
|
dir: Ref<Direction>;
|
|
45
45
|
step: Ref<DateStep | undefined>;
|
|
46
46
|
closeOnSelect: Ref<boolean>;
|
|
47
|
+
minYear: Ref<number>;
|
|
48
|
+
maxYear: Ref<number>;
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
export type DatePickerRootProps = DateFieldRootProps &
|
|
@@ -57,6 +59,8 @@ export type DatePickerRootProps = DateFieldRootProps &
|
|
|
57
59
|
| "fixedWeeks"
|
|
58
60
|
| "numberOfMonths"
|
|
59
61
|
| "preventDeselect"
|
|
62
|
+
| "minYear"
|
|
63
|
+
| "maxYear"
|
|
60
64
|
> & {
|
|
61
65
|
/** Whether or not to close the popover on date select */
|
|
62
66
|
closeOnSelect?: boolean;
|
|
@@ -96,6 +100,8 @@ const props = withDefaults(defineProps<DatePickerRootProps>(), {
|
|
|
96
100
|
isDateDisabled: undefined,
|
|
97
101
|
isDateUnavailable: undefined,
|
|
98
102
|
closeOnSelect: false,
|
|
103
|
+
minYear: 1900,
|
|
104
|
+
maxYear: 2100,
|
|
99
105
|
});
|
|
100
106
|
const emits = defineEmits<DatePickerRootEmits & PopoverRootEmits>();
|
|
101
107
|
const {
|
|
@@ -123,6 +129,8 @@ const {
|
|
|
123
129
|
dir: propDir,
|
|
124
130
|
step,
|
|
125
131
|
closeOnSelect,
|
|
132
|
+
minYear,
|
|
133
|
+
maxYear,
|
|
126
134
|
} = toRefs(props);
|
|
127
135
|
|
|
128
136
|
const dir = useDirection(propDir);
|
|
@@ -206,6 +214,8 @@ provideDatePickerRootContext({
|
|
|
206
214
|
placeholder.value = date.copy();
|
|
207
215
|
},
|
|
208
216
|
closeOnSelect,
|
|
217
|
+
minYear,
|
|
218
|
+
maxYear,
|
|
209
219
|
});
|
|
210
220
|
</script>
|
|
211
221
|
|