v-calendar-3 1.0.0
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/LICENSE +9 -0
- package/README.md +142 -0
- package/dist/cjs/index.css +1133 -0
- package/dist/cjs/index.js +10699 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/es/index.js +10678 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/style.css +1134 -0
- package/dist/iife/index.js +2 -0
- package/dist/iife/index.js.map +1 -0
- package/dist/mjs/index.mjs +10678 -0
- package/dist/mjs/index.mjs.map +1 -0
- package/dist/mjs/style.css +1134 -0
- package/dist/style.css +1134 -0
- package/dist/types/components/BaseIcon/BaseIcon.vue.d.ts +41 -0
- package/dist/types/components/BaseIcon/icons/IconChevronDown.vue.d.ts +3 -0
- package/dist/types/components/BaseIcon/icons/IconChevronLeft.vue.d.ts +3 -0
- package/dist/types/components/BaseIcon/icons/IconChevronRight.vue.d.ts +3 -0
- package/dist/types/components/BaseIcon/icons/IconClock.vue.d.ts +3 -0
- package/dist/types/components/BaseIcon/icons/index.d.ts +4 -0
- package/dist/types/components/BaseSelect/BaseSelect.vue.d.ts +26 -0
- package/dist/types/components/Calendar/Calendar.vue.d.ts +2526 -0
- package/dist/types/components/Calendar/CalendarDay.vue.d.ts +83 -0
- package/dist/types/components/Calendar/CalendarDayPopover.vue.d.ts +3 -0
- package/dist/types/components/Calendar/CalendarHeader.vue.d.ts +13 -0
- package/dist/types/components/Calendar/CalendarNav.vue.d.ts +4 -0
- package/dist/types/components/Calendar/CalendarNavPopover.vue.d.ts +3 -0
- package/dist/types/components/Calendar/CalendarPage.vue.d.ts +3 -0
- package/dist/types/components/Calendar/CalendarPageProvider.vue.d.ts +17 -0
- package/dist/types/components/Calendar/CalendarSlot.vue.d.ts +15 -0
- package/dist/types/components/Calendar/CalendarTitleSelect.vue.d.ts +15 -0
- package/dist/types/components/CalendarGrid/CalendarCell.vue.d.ts +9 -0
- package/dist/types/components/CalendarGrid/CalendarCellPopover.vue.d.ts +209 -0
- package/dist/types/components/CalendarGrid/CalendarDayCell.vue.d.ts +8 -0
- package/dist/types/components/CalendarGrid/CalendarEventDetails.vue.d.ts +15 -0
- package/dist/types/components/CalendarGrid/CalendarEventEdit.vue.d.ts +26 -0
- package/dist/types/components/CalendarGrid/CalendarGrid.vue.d.ts +134 -0
- package/dist/types/components/CalendarGrid/CalendarGridWeek.vue.d.ts +8 -0
- package/dist/types/components/CalendarGrid/CalendarViewSelect.vue.d.ts +3 -0
- package/dist/types/components/CalendarGrid/CalendarWeekCell.vue.d.ts +10 -0
- package/dist/types/components/CalendarGrid/Constraints.d.ts +16 -0
- package/dist/types/components/DatePicker/DatePicker.vue.d.ts +15251 -0
- package/dist/types/components/DatePicker/DatePickerBase.vue.d.ts +3 -0
- package/dist/types/components/DatePicker/DatePickerPopover.vue.d.ts +3 -0
- package/dist/types/components/DatePicker/TimePicker.vue.d.ts +10089 -0
- package/dist/types/components/Popover/Popover.vue.d.ts +72 -0
- package/dist/types/components/Popover/PopoverRow.vue.d.ts +17 -0
- package/dist/types/components/index.d.ts +4 -0
- package/dist/types/index.d.cts +40981 -0
- package/dist/types/index.d.mts +40981 -0
- package/dist/types/index.d.ts +40981 -0
- package/dist/types/use/base.d.ts +67 -0
- package/dist/types/use/calendar.d.ts +2422 -0
- package/dist/types/use/calendarGrid.d.ts +9370 -0
- package/dist/types/use/datePicker.d.ts +20210 -0
- package/dist/types/use/page.d.ts +20 -0
- package/dist/types/use/slots.d.ts +5 -0
- package/dist/types/use/timePicker.d.ts +10089 -0
- package/dist/types/utils/attribute.d.ts +49 -0
- package/dist/types/utils/cache.d.ts +10 -0
- package/dist/types/utils/calendar/event.d.ts +111 -0
- package/dist/types/utils/config/index.d.ts +5 -0
- package/dist/types/utils/date/helpers.d.ts +161 -0
- package/dist/types/utils/date/range.d.ts +74 -0
- package/dist/types/utils/date/repeat.d.ts +34 -0
- package/dist/types/utils/date/rules.d.ts +82 -0
- package/dist/types/utils/defaults/index.d.ts +30 -0
- package/dist/types/utils/defaults/locales.d.ts +9 -0
- package/dist/types/utils/glyph.d.ts +58 -0
- package/dist/types/utils/helpers.d.ts +49 -0
- package/dist/types/utils/locale.d.ts +48 -0
- package/dist/types/utils/page.d.ts +95 -0
- package/dist/types/utils/plugins/index.d.ts +4 -0
- package/dist/types/utils/popovers.d.ts +47 -0
- package/dist/types/utils/theme.d.ts +17 -0
- package/dist/types/utils/touch.d.ts +8 -0
- package/dist/types/utils/useDisplayMode.d.ts +12 -0
- package/dist/types/utils/watchers.d.ts +6 -0
- package/package.json +115 -0
- package/src/components/BaseIcon/BaseIcon.vue +29 -0
- package/src/components/BaseIcon/icons/IconChevronDown.vue +5 -0
- package/src/components/BaseIcon/icons/IconChevronLeft.vue +5 -0
- package/src/components/BaseIcon/icons/IconChevronRight.vue +5 -0
- package/src/components/BaseIcon/icons/IconClock.vue +11 -0
- package/src/components/BaseIcon/icons/index.ts +4 -0
- package/src/components/BaseSelect/BaseSelect.vue +136 -0
- package/src/components/Calendar/Calendar.vue +116 -0
- package/src/components/Calendar/CalendarDay.vue +422 -0
- package/src/components/Calendar/CalendarDayPopover.vue +44 -0
- package/src/components/Calendar/CalendarHeader.vue +203 -0
- package/src/components/Calendar/CalendarNav.vue +325 -0
- package/src/components/Calendar/CalendarNavPopover.vue +24 -0
- package/src/components/Calendar/CalendarPage.vue +140 -0
- package/src/components/Calendar/CalendarPageProvider.vue +15 -0
- package/src/components/Calendar/CalendarSlot.vue +36 -0
- package/src/components/Calendar/CalendarTitleSelect.vue +72 -0
- package/src/components/CalendarGrid/CalendarCell.vue +112 -0
- package/src/components/CalendarGrid/CalendarCellPopover.vue +93 -0
- package/src/components/CalendarGrid/CalendarDayCell.vue +57 -0
- package/src/components/CalendarGrid/CalendarEventDetails.vue +71 -0
- package/src/components/CalendarGrid/CalendarEventEdit.vue +195 -0
- package/src/components/CalendarGrid/CalendarGrid.vue +715 -0
- package/src/components/CalendarGrid/CalendarGridWeek.vue +60 -0
- package/src/components/CalendarGrid/CalendarViewSelect.vue +34 -0
- package/src/components/CalendarGrid/CalendarWeekCell.vue +52 -0
- package/src/components/CalendarGrid/Constraints.ts +40 -0
- package/src/components/DatePicker/DatePicker.vue +27 -0
- package/src/components/DatePicker/DatePickerBase.vue +53 -0
- package/src/components/DatePicker/DatePickerPopover.vue +37 -0
- package/src/components/DatePicker/TimePicker.vue +167 -0
- package/src/components/Popover/Popover.vue +572 -0
- package/src/components/Popover/PopoverRow.vue +94 -0
- package/src/components/index.ts +4 -0
- package/src/index.ts +24 -0
- package/src/styles/index.css +48 -0
- package/src/styles/theme.css +322 -0
- package/src/styles/transitions.css +77 -0
- package/src/use/base.ts +144 -0
- package/src/use/calendar.ts +796 -0
- package/src/use/calendarGrid.ts +954 -0
- package/src/use/datePicker.ts +909 -0
- package/src/use/page.ts +78 -0
- package/src/use/slots.ts +17 -0
- package/src/use/timePicker.ts +194 -0
- package/src/utils/attribute.ts +107 -0
- package/src/utils/cache.ts +30 -0
- package/src/utils/calendar/event.ts +335 -0
- package/src/utils/config/index.ts +11 -0
- package/src/utils/date/helpers.ts +1039 -0
- package/src/utils/date/range.ts +274 -0
- package/src/utils/date/repeat.ts +135 -0
- package/src/utils/date/rules.ts +290 -0
- package/src/utils/defaults/index.ts +77 -0
- package/src/utils/defaults/locales.ts +123 -0
- package/src/utils/defaults/masks.json +15 -0
- package/src/utils/defaults/touch.json +5 -0
- package/src/utils/glyph.ts +305 -0
- package/src/utils/helpers.ts +181 -0
- package/src/utils/locale.ts +275 -0
- package/src/utils/page.ts +608 -0
- package/src/utils/plugins/index.ts +24 -0
- package/src/utils/popovers.ts +216 -0
- package/src/utils/theme.ts +58 -0
- package/src/utils/touch.ts +72 -0
- package/src/utils/useDisplayMode.ts +198 -0
- package/src/utils/watchers.ts +26 -0
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ExtractPropTypes,
|
|
3
|
+
type PropType,
|
|
4
|
+
type SetupContext,
|
|
5
|
+
computed,
|
|
6
|
+
inject,
|
|
7
|
+
onMounted,
|
|
8
|
+
onUnmounted,
|
|
9
|
+
provide,
|
|
10
|
+
ref,
|
|
11
|
+
watch,
|
|
12
|
+
watchEffect,
|
|
13
|
+
} from 'vue';
|
|
14
|
+
import { Attribute, type AttributeConfig } from '../utils/attribute';
|
|
15
|
+
import {
|
|
16
|
+
type DateSource,
|
|
17
|
+
addDays,
|
|
18
|
+
addMonths,
|
|
19
|
+
addYears,
|
|
20
|
+
} from '../utils/date/helpers';
|
|
21
|
+
import { type DateRangeCell, DateRangeContext } from '../utils/date/range';
|
|
22
|
+
import { getDefault } from '../utils/defaults';
|
|
23
|
+
import {
|
|
24
|
+
type CustomElement,
|
|
25
|
+
arrayHasItems,
|
|
26
|
+
has,
|
|
27
|
+
head,
|
|
28
|
+
isBoolean,
|
|
29
|
+
last,
|
|
30
|
+
} from '../utils/helpers';
|
|
31
|
+
import {
|
|
32
|
+
type CalendarDay,
|
|
33
|
+
type CalendarWeek,
|
|
34
|
+
type Page,
|
|
35
|
+
type PageAddress,
|
|
36
|
+
type TitlePosition,
|
|
37
|
+
addPages as _addPages,
|
|
38
|
+
getPageAddressForDate,
|
|
39
|
+
pageIsAfterPage,
|
|
40
|
+
pageIsBeforePage,
|
|
41
|
+
pageIsBetweenPages,
|
|
42
|
+
pageIsEqualToPage,
|
|
43
|
+
pageIsValid,
|
|
44
|
+
pageRangeToArray,
|
|
45
|
+
} from '../utils/page';
|
|
46
|
+
import { type PopoverVisibility, hidePopover } from '../utils/popovers';
|
|
47
|
+
import { addHorizontalSwipeHandler } from '../utils/touch';
|
|
48
|
+
import { createWatcherController } from '../utils/watchers';
|
|
49
|
+
import { propsDef as basePropsDef, useOrCreateBase } from './base';
|
|
50
|
+
import { provideSlots } from './slots';
|
|
51
|
+
|
|
52
|
+
export type CalendarView = 'daily' | 'weekly' | 'monthly';
|
|
53
|
+
|
|
54
|
+
export type MoveTarget = DateSource | PageAddress;
|
|
55
|
+
|
|
56
|
+
export type MoveTransition = 'none' | 'fade' | 'slide-v' | 'slide-h';
|
|
57
|
+
|
|
58
|
+
export interface MoveOptions {
|
|
59
|
+
position: number;
|
|
60
|
+
view: CalendarView;
|
|
61
|
+
transition: MoveTransition;
|
|
62
|
+
force: boolean;
|
|
63
|
+
fromPage: PageAddress;
|
|
64
|
+
toPage: PageAddress;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RefreshOptions {
|
|
68
|
+
page: PageAddress;
|
|
69
|
+
position: number;
|
|
70
|
+
force: boolean;
|
|
71
|
+
transition: MoveTransition;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type DayCells = Record<
|
|
75
|
+
number,
|
|
76
|
+
{ day: CalendarDay; cells: DateRangeCell<Attribute>[] }
|
|
77
|
+
>;
|
|
78
|
+
|
|
79
|
+
export type CalendarProps = Readonly<ExtractPropTypes<typeof propsDef>>;
|
|
80
|
+
|
|
81
|
+
type IContainer = Pick<Element, 'querySelector'> & CustomElement;
|
|
82
|
+
|
|
83
|
+
export type CalendarContext = ReturnType<typeof createCalendar>;
|
|
84
|
+
|
|
85
|
+
export const propsDef = {
|
|
86
|
+
...basePropsDef,
|
|
87
|
+
view: {
|
|
88
|
+
type: String as PropType<CalendarView>,
|
|
89
|
+
default: 'monthly',
|
|
90
|
+
validator(value: string) {
|
|
91
|
+
return ['daily', 'weekly', 'monthly'].includes(value);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
rows: {
|
|
95
|
+
type: Number,
|
|
96
|
+
default: 1,
|
|
97
|
+
},
|
|
98
|
+
columns: {
|
|
99
|
+
type: Number,
|
|
100
|
+
default: 1,
|
|
101
|
+
},
|
|
102
|
+
step: Number,
|
|
103
|
+
titlePosition: {
|
|
104
|
+
type: String as PropType<TitlePosition>,
|
|
105
|
+
default: () => getDefault('titlePosition') as TitlePosition,
|
|
106
|
+
},
|
|
107
|
+
navVisibility: {
|
|
108
|
+
type: String as PropType<PopoverVisibility>,
|
|
109
|
+
default: () => getDefault('navVisibility') as PopoverVisibility,
|
|
110
|
+
},
|
|
111
|
+
showWeeknumbers: [Boolean, String],
|
|
112
|
+
showIsoWeeknumbers: [Boolean, String],
|
|
113
|
+
expanded: Boolean,
|
|
114
|
+
borderless: Boolean,
|
|
115
|
+
transparent: Boolean,
|
|
116
|
+
initialPage: Object as PropType<PageAddress>,
|
|
117
|
+
initialPagePosition: { type: Number, default: 1 },
|
|
118
|
+
minPage: Object as PropType<PageAddress>,
|
|
119
|
+
maxPage: Object as PropType<PageAddress>,
|
|
120
|
+
transition: String as PropType<MoveTransition>,
|
|
121
|
+
attributes: Array as PropType<Array<AttributeConfig>>,
|
|
122
|
+
trimWeeks: Boolean,
|
|
123
|
+
disablePageSwipe: Boolean,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const emitsDef = [
|
|
127
|
+
'dayclick',
|
|
128
|
+
'daymouseenter',
|
|
129
|
+
'daymouseleave',
|
|
130
|
+
'dayfocusin',
|
|
131
|
+
'dayfocusout',
|
|
132
|
+
'daykeydown',
|
|
133
|
+
'weeknumberclick',
|
|
134
|
+
'transition-start',
|
|
135
|
+
'transition-end',
|
|
136
|
+
'did-move',
|
|
137
|
+
'update:view',
|
|
138
|
+
'update:pages',
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const contextKey = Symbol('__vc_calendar_context__');
|
|
142
|
+
|
|
143
|
+
export function createCalendar(
|
|
144
|
+
props: CalendarProps,
|
|
145
|
+
{ slots, emit }: Pick<SetupContext, 'slots' | 'emit'>,
|
|
146
|
+
) {
|
|
147
|
+
// #region Refs
|
|
148
|
+
|
|
149
|
+
const containerRef = ref<IContainer | null>(null);
|
|
150
|
+
const focusedDay = ref<CalendarDay | null>(null);
|
|
151
|
+
const focusableDay = ref(new Date().getDate());
|
|
152
|
+
const inTransition = ref(false);
|
|
153
|
+
const navPopoverId = ref(Symbol());
|
|
154
|
+
const dayPopoverId = ref(Symbol());
|
|
155
|
+
const _view = ref(props.view);
|
|
156
|
+
const _pages = ref<Page[]>([]);
|
|
157
|
+
const transitionName = ref('');
|
|
158
|
+
|
|
159
|
+
// #endregion
|
|
160
|
+
|
|
161
|
+
// Non-reactive util vars
|
|
162
|
+
let transitionPromise: any = null;
|
|
163
|
+
let removeHandlers: any = null;
|
|
164
|
+
const { handleWatcher, skipWatcher } = createWatcherController();
|
|
165
|
+
|
|
166
|
+
provideSlots(slots);
|
|
167
|
+
|
|
168
|
+
const {
|
|
169
|
+
theme,
|
|
170
|
+
color,
|
|
171
|
+
displayMode,
|
|
172
|
+
locale,
|
|
173
|
+
masks,
|
|
174
|
+
minDate,
|
|
175
|
+
maxDate,
|
|
176
|
+
normalizedMinDate,
|
|
177
|
+
normalizedMaxDate,
|
|
178
|
+
disabledAttribute,
|
|
179
|
+
disabledDates,
|
|
180
|
+
} = useOrCreateBase(props);
|
|
181
|
+
|
|
182
|
+
// #region Computed
|
|
183
|
+
|
|
184
|
+
const count = computed(() => props.rows * props.columns);
|
|
185
|
+
|
|
186
|
+
const step = computed(() => props.step || count.value);
|
|
187
|
+
|
|
188
|
+
const firstPage = computed(() => head(_pages.value) ?? null);
|
|
189
|
+
|
|
190
|
+
const lastPage = computed(() => last(_pages.value) ?? null);
|
|
191
|
+
|
|
192
|
+
const minPage = computed(
|
|
193
|
+
() =>
|
|
194
|
+
props.minPage ||
|
|
195
|
+
(normalizedMinDate.value
|
|
196
|
+
? getDateAddress(normalizedMinDate.value)
|
|
197
|
+
: null),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const maxPage = computed(
|
|
201
|
+
() =>
|
|
202
|
+
props.maxPage ||
|
|
203
|
+
(normalizedMaxDate.value
|
|
204
|
+
? getDateAddress(normalizedMaxDate.value)
|
|
205
|
+
: null),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const navVisibility = computed(() => props.navVisibility);
|
|
209
|
+
|
|
210
|
+
const showWeeknumbers = computed(() => !!props.showWeeknumbers);
|
|
211
|
+
|
|
212
|
+
const showIsoWeeknumbers = computed(() => !!props.showIsoWeeknumbers);
|
|
213
|
+
|
|
214
|
+
const isMonthly = computed(() => _view.value === 'monthly');
|
|
215
|
+
const isWeekly = computed(() => _view.value === 'weekly');
|
|
216
|
+
const isDaily = computed(() => _view.value === 'daily');
|
|
217
|
+
|
|
218
|
+
// #endregion Computed
|
|
219
|
+
|
|
220
|
+
// #region Methods
|
|
221
|
+
|
|
222
|
+
const onTransitionBeforeEnter = () => {
|
|
223
|
+
inTransition.value = true;
|
|
224
|
+
emit('transition-start');
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const onTransitionAfterEnter = () => {
|
|
228
|
+
inTransition.value = false;
|
|
229
|
+
emit('transition-end');
|
|
230
|
+
if (transitionPromise) {
|
|
231
|
+
transitionPromise.resolve(true);
|
|
232
|
+
transitionPromise = null;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const addPages = (
|
|
237
|
+
address: PageAddress,
|
|
238
|
+
count: number,
|
|
239
|
+
view = _view.value,
|
|
240
|
+
) => {
|
|
241
|
+
return _addPages(address, count, view, locale.value);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const getDateAddress = (date: DateSource) => {
|
|
245
|
+
return getPageAddressForDate(date, _view.value, locale.value);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const refreshDisabled = (day: CalendarDay) => {
|
|
249
|
+
if (!disabledAttribute.value || !attributeContext.value) return;
|
|
250
|
+
day.isDisabled = attributeContext.value.cellExists(
|
|
251
|
+
disabledAttribute.value.key,
|
|
252
|
+
day.dayIndex,
|
|
253
|
+
);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const refreshFocusable = (day: CalendarDay) => {
|
|
257
|
+
day.isFocusable = day.inMonth && day.day === focusableDay.value;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const forDays = (pages: Page[], fn: (day: CalendarDay) => boolean | void) => {
|
|
261
|
+
for (const page of pages) {
|
|
262
|
+
for (const day of page.days) {
|
|
263
|
+
if (fn(day) === false) return;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const days = computed(() =>
|
|
269
|
+
_pages.value.reduce((result: CalendarDay[], page: Page) => {
|
|
270
|
+
result.push(...page.viewDays);
|
|
271
|
+
return result;
|
|
272
|
+
}, [] as CalendarDay[]),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const attributes = computed(() => {
|
|
276
|
+
const result: Attribute[] = [];
|
|
277
|
+
(props.attributes || []).forEach((attr, i) => {
|
|
278
|
+
if (!attr || !attr.dates) return;
|
|
279
|
+
result.push(
|
|
280
|
+
new Attribute(
|
|
281
|
+
{
|
|
282
|
+
...attr,
|
|
283
|
+
order: attr.order || 0,
|
|
284
|
+
},
|
|
285
|
+
theme.value,
|
|
286
|
+
locale.value,
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
if (disabledAttribute.value) {
|
|
291
|
+
result.push(disabledAttribute.value);
|
|
292
|
+
}
|
|
293
|
+
return result;
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const hasAttributes = computed(() => arrayHasItems(attributes.value));
|
|
297
|
+
|
|
298
|
+
const attributeContext = computed(() => {
|
|
299
|
+
const ctx = new DateRangeContext();
|
|
300
|
+
attributes.value.forEach(attr => {
|
|
301
|
+
attr.ranges.forEach(range => {
|
|
302
|
+
ctx.render(attr, range, days.value);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
return ctx;
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const dayCells = computed(() => {
|
|
309
|
+
return days.value.reduce((result, day) => {
|
|
310
|
+
result[day.dayIndex] = { day, cells: [] };
|
|
311
|
+
result[day.dayIndex].cells.push(...attributeContext.value.getCells(day));
|
|
312
|
+
return result;
|
|
313
|
+
}, {} as DayCells);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const getWeeknumberPosition = (column: number, columnFromEnd: number) => {
|
|
317
|
+
const showWeeknumbers = props.showWeeknumbers || props.showIsoWeeknumbers;
|
|
318
|
+
if (showWeeknumbers == null) return '';
|
|
319
|
+
if (isBoolean(showWeeknumbers)) {
|
|
320
|
+
return showWeeknumbers ? 'left' : '';
|
|
321
|
+
}
|
|
322
|
+
if (showWeeknumbers.startsWith('right')) {
|
|
323
|
+
return columnFromEnd > 1 ? 'right' : showWeeknumbers;
|
|
324
|
+
}
|
|
325
|
+
return column > 1 ? 'left' : showWeeknumbers;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const getPageForAttributes = () => {
|
|
329
|
+
if (!hasAttributes.value) return null;
|
|
330
|
+
const attr =
|
|
331
|
+
attributes.value.find(attr => attr.pinPage) || attributes.value[0];
|
|
332
|
+
if (!attr || !attr.hasRanges) return null;
|
|
333
|
+
const [range] = attr.ranges;
|
|
334
|
+
const date = range.start?.date || range.end?.date;
|
|
335
|
+
return date ? getDateAddress(date) : null;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const getDefaultInitialPage = () => {
|
|
339
|
+
// 1. Try existing first page
|
|
340
|
+
if (pageIsValid(firstPage.value)) return firstPage.value as PageAddress;
|
|
341
|
+
// 2. Try the first attribute
|
|
342
|
+
const page = getPageForAttributes();
|
|
343
|
+
if (pageIsValid(page)) return page as PageAddress;
|
|
344
|
+
// 3. Use today's page
|
|
345
|
+
return getDateAddress(new Date());
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const getTargetPageRange = (
|
|
349
|
+
page: PageAddress,
|
|
350
|
+
opts: Partial<MoveOptions> = {},
|
|
351
|
+
) => {
|
|
352
|
+
const { view = _view.value, position = 1, force } = opts;
|
|
353
|
+
const pagesToAdd = position > 0 ? 1 - position : -(count.value + position);
|
|
354
|
+
let fromPage = addPages(page, pagesToAdd, view);
|
|
355
|
+
let toPage = addPages(fromPage!, count.value - 1, view);
|
|
356
|
+
|
|
357
|
+
// Adjust range for min/max if not forced
|
|
358
|
+
if (!force) {
|
|
359
|
+
if (pageIsBeforePage(fromPage, minPage.value)) {
|
|
360
|
+
fromPage = minPage.value!;
|
|
361
|
+
} else if (pageIsAfterPage(toPage, maxPage.value)) {
|
|
362
|
+
fromPage = addPages(maxPage.value!, 1 - count.value);
|
|
363
|
+
}
|
|
364
|
+
toPage = addPages(fromPage!, count.value - 1);
|
|
365
|
+
}
|
|
366
|
+
return { fromPage, toPage };
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const getPageTransition = (
|
|
370
|
+
oldPage: Page,
|
|
371
|
+
newPage: Page,
|
|
372
|
+
defaultTransition = '',
|
|
373
|
+
) => {
|
|
374
|
+
if (defaultTransition === 'none' || defaultTransition === 'fade')
|
|
375
|
+
return defaultTransition;
|
|
376
|
+
// Moving to a different view
|
|
377
|
+
if (oldPage?.view !== newPage?.view) return 'fade';
|
|
378
|
+
// Moving to a previous page
|
|
379
|
+
const moveNext = pageIsAfterPage(newPage, oldPage);
|
|
380
|
+
const movePrev = pageIsBeforePage(newPage, oldPage);
|
|
381
|
+
if (!moveNext && !movePrev) {
|
|
382
|
+
return 'fade';
|
|
383
|
+
}
|
|
384
|
+
// Vertical slide
|
|
385
|
+
if (defaultTransition === 'slide-v') {
|
|
386
|
+
return movePrev ? 'slide-down' : 'slide-up';
|
|
387
|
+
}
|
|
388
|
+
// Horizontal slide
|
|
389
|
+
return movePrev ? 'slide-right' : 'slide-left';
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const refreshPages = (opts: Partial<RefreshOptions> = {}) => {
|
|
393
|
+
return new Promise((resolve, reject) => {
|
|
394
|
+
const { position = 1, force = false, transition } = opts;
|
|
395
|
+
const page = pageIsValid(opts.page)
|
|
396
|
+
? opts.page!
|
|
397
|
+
: getDefaultInitialPage();
|
|
398
|
+
const { fromPage } = getTargetPageRange(page, {
|
|
399
|
+
position,
|
|
400
|
+
force,
|
|
401
|
+
});
|
|
402
|
+
// Create the new pages
|
|
403
|
+
const pages: Page[] = [];
|
|
404
|
+
for (let i = 0; i < count.value; i++) {
|
|
405
|
+
const newPage = addPages(fromPage!, i);
|
|
406
|
+
const position = i + 1;
|
|
407
|
+
const row = Math.ceil(position / props.columns);
|
|
408
|
+
const rowFromEnd = props.rows - row + 1;
|
|
409
|
+
const column = position % props.columns || props.columns;
|
|
410
|
+
const columnFromEnd = props.columns - column + 1;
|
|
411
|
+
const weeknumberPosition = getWeeknumberPosition(column, columnFromEnd);
|
|
412
|
+
pages.push(
|
|
413
|
+
locale.value.getPage({
|
|
414
|
+
...newPage,
|
|
415
|
+
view: _view.value,
|
|
416
|
+
titlePosition: props.titlePosition,
|
|
417
|
+
trimWeeks: props.trimWeeks,
|
|
418
|
+
position,
|
|
419
|
+
row,
|
|
420
|
+
rowFromEnd,
|
|
421
|
+
column,
|
|
422
|
+
columnFromEnd,
|
|
423
|
+
showWeeknumbers: showWeeknumbers.value,
|
|
424
|
+
showIsoWeeknumbers: showIsoWeeknumbers.value,
|
|
425
|
+
weeknumberPosition,
|
|
426
|
+
}),
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
// Assign the transition
|
|
430
|
+
transitionName.value = getPageTransition(
|
|
431
|
+
_pages.value[0],
|
|
432
|
+
pages[0],
|
|
433
|
+
transition,
|
|
434
|
+
);
|
|
435
|
+
// Assign the new pages
|
|
436
|
+
_pages.value = pages;
|
|
437
|
+
// Cache or resolve transition promise
|
|
438
|
+
if (transitionName.value && transitionName.value !== 'none') {
|
|
439
|
+
transitionPromise = {
|
|
440
|
+
resolve,
|
|
441
|
+
reject,
|
|
442
|
+
};
|
|
443
|
+
} else {
|
|
444
|
+
resolve(true);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const targetBy = (pages: number) => {
|
|
450
|
+
const fromPage = firstPage.value ?? getDateAddress(new Date());
|
|
451
|
+
return addPages(fromPage, pages);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const canMove = (target: MoveTarget, opts: Partial<MoveOptions> = {}) => {
|
|
455
|
+
const page = pageIsValid(target as PageAddress)
|
|
456
|
+
? (target as Page)
|
|
457
|
+
: getDateAddress(target as DateSource);
|
|
458
|
+
// Calculate new page range without adjusting to min/max
|
|
459
|
+
Object.assign(
|
|
460
|
+
opts,
|
|
461
|
+
getTargetPageRange(page, {
|
|
462
|
+
...opts,
|
|
463
|
+
force: true,
|
|
464
|
+
}),
|
|
465
|
+
);
|
|
466
|
+
// Verify we can move to any pages in the target range
|
|
467
|
+
const pagesInRange = pageRangeToArray(
|
|
468
|
+
opts.fromPage!,
|
|
469
|
+
opts.toPage!,
|
|
470
|
+
_view.value,
|
|
471
|
+
locale.value,
|
|
472
|
+
).map(p => pageIsBetweenPages(p, minPage.value, maxPage.value));
|
|
473
|
+
return pagesInRange.some(val => val);
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const canMoveBy = (pages: number, opts: Partial<MoveOptions> = {}) => {
|
|
477
|
+
return canMove(targetBy(pages), opts);
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const canMovePrev = computed(() => canMoveBy(-step.value));
|
|
481
|
+
|
|
482
|
+
const canMoveNext = computed(() => canMoveBy(step.value));
|
|
483
|
+
|
|
484
|
+
const move = async (target: MoveTarget, opts: Partial<MoveOptions> = {}) => {
|
|
485
|
+
// Return if we can't move to this page
|
|
486
|
+
if (!opts.force && !canMove(target, opts)) return false;
|
|
487
|
+
// Move to new `fromPage` if it's different from the current one
|
|
488
|
+
if (opts.fromPage && !pageIsEqualToPage(opts.fromPage, firstPage.value)) {
|
|
489
|
+
// Hide nav popover for good measure
|
|
490
|
+
hidePopover({
|
|
491
|
+
id: navPopoverId.value,
|
|
492
|
+
target: containerRef.value,
|
|
493
|
+
hideDelay: 0,
|
|
494
|
+
});
|
|
495
|
+
// Quietly change view if needed
|
|
496
|
+
if (opts.view) {
|
|
497
|
+
skipWatcher('view', 10);
|
|
498
|
+
_view.value = opts.view;
|
|
499
|
+
}
|
|
500
|
+
await refreshPages({
|
|
501
|
+
...opts,
|
|
502
|
+
page: opts.fromPage,
|
|
503
|
+
position: 1,
|
|
504
|
+
force: true,
|
|
505
|
+
});
|
|
506
|
+
emit('did-move', _pages.value);
|
|
507
|
+
}
|
|
508
|
+
return true;
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const moveBy = (pages: number, opts: Partial<MoveOptions> = {}) => {
|
|
512
|
+
return move(targetBy(pages), opts);
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const movePrev = () => {
|
|
516
|
+
return moveBy(-step.value);
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const moveNext = () => {
|
|
520
|
+
return moveBy(step.value);
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const tryFocusDate = (date: Date) => {
|
|
524
|
+
const inMonth = isMonthly.value ? '.in-month' : '';
|
|
525
|
+
const daySelector = `.id-${locale.value.getDayId(date)}${inMonth}`;
|
|
526
|
+
const selector = `${daySelector}.vc-focusable, ${daySelector} .vc-focusable`;
|
|
527
|
+
const el = containerRef.value;
|
|
528
|
+
if (el) {
|
|
529
|
+
const focusableEl = el.querySelector(selector) as HTMLElement;
|
|
530
|
+
if (focusableEl) {
|
|
531
|
+
focusableEl.focus();
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return false;
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const focusDate = async (date: Date, opts: Partial<MoveOptions> = {}) => {
|
|
539
|
+
if (tryFocusDate(date)) return true;
|
|
540
|
+
// Move to the given date
|
|
541
|
+
await move(date, opts);
|
|
542
|
+
return tryFocusDate(date);
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
const onDayClick = (day: CalendarDay, event: MouseEvent) => {
|
|
546
|
+
focusableDay.value = day.day;
|
|
547
|
+
emit('dayclick', day, event);
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
const onDayMouseenter = (day: CalendarDay, event: MouseEvent) => {
|
|
551
|
+
emit('daymouseenter', day, event);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const onDayMouseleave = (day: CalendarDay, event: MouseEvent) => {
|
|
555
|
+
emit('daymouseleave', day, event);
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
const onDayFocusin = (day: CalendarDay, event: FocusEvent | null) => {
|
|
559
|
+
focusableDay.value = day.day;
|
|
560
|
+
focusedDay.value = day;
|
|
561
|
+
day.isFocused = true;
|
|
562
|
+
emit('dayfocusin', day, event);
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const onDayFocusout = (day: CalendarDay, event: FocusEvent) => {
|
|
566
|
+
focusedDay.value = null;
|
|
567
|
+
day.isFocused = false;
|
|
568
|
+
emit('dayfocusout', day, event);
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
const onDayKeydown = (day: CalendarDay, event: KeyboardEvent) => {
|
|
572
|
+
emit('daykeydown', day, event);
|
|
573
|
+
const date = day.noonDate;
|
|
574
|
+
let newDate = null;
|
|
575
|
+
switch (event.key) {
|
|
576
|
+
case 'ArrowLeft': {
|
|
577
|
+
// Move to previous day
|
|
578
|
+
newDate = addDays(date, -1);
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
case 'ArrowRight': {
|
|
582
|
+
// Move to next day
|
|
583
|
+
newDate = addDays(date, 1);
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
case 'ArrowUp': {
|
|
587
|
+
// Move to previous week
|
|
588
|
+
newDate = addDays(date, -7);
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
case 'ArrowDown': {
|
|
592
|
+
// Move to next week
|
|
593
|
+
newDate = addDays(date, 7);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
case 'Home': {
|
|
597
|
+
// Move to first weekday position
|
|
598
|
+
newDate = addDays(date, -day.weekdayPosition + 1);
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
case 'End': {
|
|
602
|
+
// Move to last weekday position
|
|
603
|
+
newDate = addDays(date, day.weekdayPositionFromEnd);
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
case 'PageUp': {
|
|
607
|
+
if (event.altKey) {
|
|
608
|
+
// Move to previous year w/ Alt/Option key
|
|
609
|
+
newDate = addYears(date, -1);
|
|
610
|
+
} else {
|
|
611
|
+
// Move to previous month
|
|
612
|
+
newDate = addMonths(date, -1);
|
|
613
|
+
}
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
case 'PageDown': {
|
|
617
|
+
if (event.altKey) {
|
|
618
|
+
// Move to next year w/ Alt/Option key
|
|
619
|
+
newDate = addYears(date, 1);
|
|
620
|
+
} else {
|
|
621
|
+
// Move to next month
|
|
622
|
+
newDate = addMonths(date, 1);
|
|
623
|
+
}
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (newDate) {
|
|
628
|
+
event.preventDefault();
|
|
629
|
+
focusDate(newDate).catch();
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
const onKeydown = (event: KeyboardEvent) => {
|
|
634
|
+
const day = focusedDay.value;
|
|
635
|
+
if (day != null) {
|
|
636
|
+
onDayKeydown(day, event);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
const onWeeknumberClick = (week: CalendarWeek, event: MouseEvent) => {
|
|
641
|
+
emit('weeknumberclick', week, event);
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
// #endregion Methods
|
|
645
|
+
|
|
646
|
+
// #region Lifecycle methods
|
|
647
|
+
|
|
648
|
+
// Created
|
|
649
|
+
|
|
650
|
+
refreshPages({
|
|
651
|
+
page: props.initialPage,
|
|
652
|
+
position: props.initialPagePosition,
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
// Mounted
|
|
656
|
+
|
|
657
|
+
onMounted(() => {
|
|
658
|
+
if (!props.disablePageSwipe && containerRef.value) {
|
|
659
|
+
// Add swipe handler to move to next and previous pages
|
|
660
|
+
removeHandlers = addHorizontalSwipeHandler(
|
|
661
|
+
containerRef.value,
|
|
662
|
+
({ toLeft = false, toRight = false }) => {
|
|
663
|
+
if (toLeft) {
|
|
664
|
+
moveNext();
|
|
665
|
+
} else if (toRight) {
|
|
666
|
+
movePrev();
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
getDefault('touch'),
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// Unmounted
|
|
675
|
+
onUnmounted(() => {
|
|
676
|
+
_pages.value = [];
|
|
677
|
+
if (removeHandlers) removeHandlers();
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// #endregion Lifecycle methods
|
|
681
|
+
|
|
682
|
+
// #region Watch
|
|
683
|
+
|
|
684
|
+
watch(
|
|
685
|
+
() => locale.value,
|
|
686
|
+
() => {
|
|
687
|
+
refreshPages();
|
|
688
|
+
},
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
watch(
|
|
692
|
+
() => count.value,
|
|
693
|
+
() => refreshPages(),
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
watch(
|
|
697
|
+
() => props.view,
|
|
698
|
+
() => (_view.value = props.view),
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
watch(
|
|
702
|
+
() => _view.value,
|
|
703
|
+
() => {
|
|
704
|
+
handleWatcher('view', () => {
|
|
705
|
+
refreshPages();
|
|
706
|
+
});
|
|
707
|
+
emit('update:view', _view.value);
|
|
708
|
+
},
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
watch(
|
|
712
|
+
() => focusableDay.value,
|
|
713
|
+
() => {
|
|
714
|
+
forDays(_pages.value, day => refreshFocusable(day));
|
|
715
|
+
},
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
watchEffect(() => {
|
|
719
|
+
emit('update:pages', _pages.value);
|
|
720
|
+
// Refresh state for days
|
|
721
|
+
forDays(_pages.value, day => {
|
|
722
|
+
// Refresh disabled state
|
|
723
|
+
refreshDisabled(day);
|
|
724
|
+
// Refresh focusable state
|
|
725
|
+
refreshFocusable(day);
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
// #endregion Watch
|
|
730
|
+
|
|
731
|
+
const context = {
|
|
732
|
+
emit,
|
|
733
|
+
containerRef,
|
|
734
|
+
focusedDay,
|
|
735
|
+
inTransition,
|
|
736
|
+
navPopoverId,
|
|
737
|
+
dayPopoverId,
|
|
738
|
+
view: _view,
|
|
739
|
+
pages: _pages,
|
|
740
|
+
transitionName,
|
|
741
|
+
theme,
|
|
742
|
+
color,
|
|
743
|
+
displayMode,
|
|
744
|
+
locale,
|
|
745
|
+
masks,
|
|
746
|
+
attributes,
|
|
747
|
+
disabledAttribute,
|
|
748
|
+
disabledDates,
|
|
749
|
+
attributeContext,
|
|
750
|
+
days,
|
|
751
|
+
dayCells,
|
|
752
|
+
count,
|
|
753
|
+
step,
|
|
754
|
+
firstPage,
|
|
755
|
+
lastPage,
|
|
756
|
+
canMovePrev,
|
|
757
|
+
canMoveNext,
|
|
758
|
+
minPage,
|
|
759
|
+
maxPage,
|
|
760
|
+
isMonthly,
|
|
761
|
+
isWeekly,
|
|
762
|
+
isDaily,
|
|
763
|
+
navVisibility,
|
|
764
|
+
showWeeknumbers,
|
|
765
|
+
showIsoWeeknumbers,
|
|
766
|
+
getDateAddress,
|
|
767
|
+
canMove,
|
|
768
|
+
canMoveBy,
|
|
769
|
+
move,
|
|
770
|
+
moveBy,
|
|
771
|
+
movePrev,
|
|
772
|
+
moveNext,
|
|
773
|
+
onTransitionBeforeEnter,
|
|
774
|
+
onTransitionAfterEnter,
|
|
775
|
+
tryFocusDate,
|
|
776
|
+
focusDate,
|
|
777
|
+
onKeydown,
|
|
778
|
+
onDayKeydown,
|
|
779
|
+
onDayClick,
|
|
780
|
+
onDayMouseenter,
|
|
781
|
+
onDayMouseleave,
|
|
782
|
+
onDayFocusin,
|
|
783
|
+
onDayFocusout,
|
|
784
|
+
onWeeknumberClick,
|
|
785
|
+
};
|
|
786
|
+
provide(contextKey, context);
|
|
787
|
+
return context;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
export function useCalendar(): CalendarContext {
|
|
791
|
+
const context = inject<CalendarContext>(contextKey);
|
|
792
|
+
if (context) return context;
|
|
793
|
+
throw new Error(
|
|
794
|
+
'Calendar context missing. Please verify this component is nested within a valid context provider.',
|
|
795
|
+
);
|
|
796
|
+
}
|