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.
Files changed (147) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +142 -0
  3. package/dist/cjs/index.css +1133 -0
  4. package/dist/cjs/index.js +10699 -0
  5. package/dist/cjs/index.js.map +1 -0
  6. package/dist/cjs/package.json +1 -0
  7. package/dist/es/index.js +10678 -0
  8. package/dist/es/index.js.map +1 -0
  9. package/dist/es/style.css +1134 -0
  10. package/dist/iife/index.js +2 -0
  11. package/dist/iife/index.js.map +1 -0
  12. package/dist/mjs/index.mjs +10678 -0
  13. package/dist/mjs/index.mjs.map +1 -0
  14. package/dist/mjs/style.css +1134 -0
  15. package/dist/style.css +1134 -0
  16. package/dist/types/components/BaseIcon/BaseIcon.vue.d.ts +41 -0
  17. package/dist/types/components/BaseIcon/icons/IconChevronDown.vue.d.ts +3 -0
  18. package/dist/types/components/BaseIcon/icons/IconChevronLeft.vue.d.ts +3 -0
  19. package/dist/types/components/BaseIcon/icons/IconChevronRight.vue.d.ts +3 -0
  20. package/dist/types/components/BaseIcon/icons/IconClock.vue.d.ts +3 -0
  21. package/dist/types/components/BaseIcon/icons/index.d.ts +4 -0
  22. package/dist/types/components/BaseSelect/BaseSelect.vue.d.ts +26 -0
  23. package/dist/types/components/Calendar/Calendar.vue.d.ts +2526 -0
  24. package/dist/types/components/Calendar/CalendarDay.vue.d.ts +83 -0
  25. package/dist/types/components/Calendar/CalendarDayPopover.vue.d.ts +3 -0
  26. package/dist/types/components/Calendar/CalendarHeader.vue.d.ts +13 -0
  27. package/dist/types/components/Calendar/CalendarNav.vue.d.ts +4 -0
  28. package/dist/types/components/Calendar/CalendarNavPopover.vue.d.ts +3 -0
  29. package/dist/types/components/Calendar/CalendarPage.vue.d.ts +3 -0
  30. package/dist/types/components/Calendar/CalendarPageProvider.vue.d.ts +17 -0
  31. package/dist/types/components/Calendar/CalendarSlot.vue.d.ts +15 -0
  32. package/dist/types/components/Calendar/CalendarTitleSelect.vue.d.ts +15 -0
  33. package/dist/types/components/CalendarGrid/CalendarCell.vue.d.ts +9 -0
  34. package/dist/types/components/CalendarGrid/CalendarCellPopover.vue.d.ts +209 -0
  35. package/dist/types/components/CalendarGrid/CalendarDayCell.vue.d.ts +8 -0
  36. package/dist/types/components/CalendarGrid/CalendarEventDetails.vue.d.ts +15 -0
  37. package/dist/types/components/CalendarGrid/CalendarEventEdit.vue.d.ts +26 -0
  38. package/dist/types/components/CalendarGrid/CalendarGrid.vue.d.ts +134 -0
  39. package/dist/types/components/CalendarGrid/CalendarGridWeek.vue.d.ts +8 -0
  40. package/dist/types/components/CalendarGrid/CalendarViewSelect.vue.d.ts +3 -0
  41. package/dist/types/components/CalendarGrid/CalendarWeekCell.vue.d.ts +10 -0
  42. package/dist/types/components/CalendarGrid/Constraints.d.ts +16 -0
  43. package/dist/types/components/DatePicker/DatePicker.vue.d.ts +15251 -0
  44. package/dist/types/components/DatePicker/DatePickerBase.vue.d.ts +3 -0
  45. package/dist/types/components/DatePicker/DatePickerPopover.vue.d.ts +3 -0
  46. package/dist/types/components/DatePicker/TimePicker.vue.d.ts +10089 -0
  47. package/dist/types/components/Popover/Popover.vue.d.ts +72 -0
  48. package/dist/types/components/Popover/PopoverRow.vue.d.ts +17 -0
  49. package/dist/types/components/index.d.ts +4 -0
  50. package/dist/types/index.d.cts +40981 -0
  51. package/dist/types/index.d.mts +40981 -0
  52. package/dist/types/index.d.ts +40981 -0
  53. package/dist/types/use/base.d.ts +67 -0
  54. package/dist/types/use/calendar.d.ts +2422 -0
  55. package/dist/types/use/calendarGrid.d.ts +9370 -0
  56. package/dist/types/use/datePicker.d.ts +20210 -0
  57. package/dist/types/use/page.d.ts +20 -0
  58. package/dist/types/use/slots.d.ts +5 -0
  59. package/dist/types/use/timePicker.d.ts +10089 -0
  60. package/dist/types/utils/attribute.d.ts +49 -0
  61. package/dist/types/utils/cache.d.ts +10 -0
  62. package/dist/types/utils/calendar/event.d.ts +111 -0
  63. package/dist/types/utils/config/index.d.ts +5 -0
  64. package/dist/types/utils/date/helpers.d.ts +161 -0
  65. package/dist/types/utils/date/range.d.ts +74 -0
  66. package/dist/types/utils/date/repeat.d.ts +34 -0
  67. package/dist/types/utils/date/rules.d.ts +82 -0
  68. package/dist/types/utils/defaults/index.d.ts +30 -0
  69. package/dist/types/utils/defaults/locales.d.ts +9 -0
  70. package/dist/types/utils/glyph.d.ts +58 -0
  71. package/dist/types/utils/helpers.d.ts +49 -0
  72. package/dist/types/utils/locale.d.ts +48 -0
  73. package/dist/types/utils/page.d.ts +95 -0
  74. package/dist/types/utils/plugins/index.d.ts +4 -0
  75. package/dist/types/utils/popovers.d.ts +47 -0
  76. package/dist/types/utils/theme.d.ts +17 -0
  77. package/dist/types/utils/touch.d.ts +8 -0
  78. package/dist/types/utils/useDisplayMode.d.ts +12 -0
  79. package/dist/types/utils/watchers.d.ts +6 -0
  80. package/package.json +115 -0
  81. package/src/components/BaseIcon/BaseIcon.vue +29 -0
  82. package/src/components/BaseIcon/icons/IconChevronDown.vue +5 -0
  83. package/src/components/BaseIcon/icons/IconChevronLeft.vue +5 -0
  84. package/src/components/BaseIcon/icons/IconChevronRight.vue +5 -0
  85. package/src/components/BaseIcon/icons/IconClock.vue +11 -0
  86. package/src/components/BaseIcon/icons/index.ts +4 -0
  87. package/src/components/BaseSelect/BaseSelect.vue +136 -0
  88. package/src/components/Calendar/Calendar.vue +116 -0
  89. package/src/components/Calendar/CalendarDay.vue +422 -0
  90. package/src/components/Calendar/CalendarDayPopover.vue +44 -0
  91. package/src/components/Calendar/CalendarHeader.vue +203 -0
  92. package/src/components/Calendar/CalendarNav.vue +325 -0
  93. package/src/components/Calendar/CalendarNavPopover.vue +24 -0
  94. package/src/components/Calendar/CalendarPage.vue +140 -0
  95. package/src/components/Calendar/CalendarPageProvider.vue +15 -0
  96. package/src/components/Calendar/CalendarSlot.vue +36 -0
  97. package/src/components/Calendar/CalendarTitleSelect.vue +72 -0
  98. package/src/components/CalendarGrid/CalendarCell.vue +112 -0
  99. package/src/components/CalendarGrid/CalendarCellPopover.vue +93 -0
  100. package/src/components/CalendarGrid/CalendarDayCell.vue +57 -0
  101. package/src/components/CalendarGrid/CalendarEventDetails.vue +71 -0
  102. package/src/components/CalendarGrid/CalendarEventEdit.vue +195 -0
  103. package/src/components/CalendarGrid/CalendarGrid.vue +715 -0
  104. package/src/components/CalendarGrid/CalendarGridWeek.vue +60 -0
  105. package/src/components/CalendarGrid/CalendarViewSelect.vue +34 -0
  106. package/src/components/CalendarGrid/CalendarWeekCell.vue +52 -0
  107. package/src/components/CalendarGrid/Constraints.ts +40 -0
  108. package/src/components/DatePicker/DatePicker.vue +27 -0
  109. package/src/components/DatePicker/DatePickerBase.vue +53 -0
  110. package/src/components/DatePicker/DatePickerPopover.vue +37 -0
  111. package/src/components/DatePicker/TimePicker.vue +167 -0
  112. package/src/components/Popover/Popover.vue +572 -0
  113. package/src/components/Popover/PopoverRow.vue +94 -0
  114. package/src/components/index.ts +4 -0
  115. package/src/index.ts +24 -0
  116. package/src/styles/index.css +48 -0
  117. package/src/styles/theme.css +322 -0
  118. package/src/styles/transitions.css +77 -0
  119. package/src/use/base.ts +144 -0
  120. package/src/use/calendar.ts +796 -0
  121. package/src/use/calendarGrid.ts +954 -0
  122. package/src/use/datePicker.ts +909 -0
  123. package/src/use/page.ts +78 -0
  124. package/src/use/slots.ts +17 -0
  125. package/src/use/timePicker.ts +194 -0
  126. package/src/utils/attribute.ts +107 -0
  127. package/src/utils/cache.ts +30 -0
  128. package/src/utils/calendar/event.ts +335 -0
  129. package/src/utils/config/index.ts +11 -0
  130. package/src/utils/date/helpers.ts +1039 -0
  131. package/src/utils/date/range.ts +274 -0
  132. package/src/utils/date/repeat.ts +135 -0
  133. package/src/utils/date/rules.ts +290 -0
  134. package/src/utils/defaults/index.ts +77 -0
  135. package/src/utils/defaults/locales.ts +123 -0
  136. package/src/utils/defaults/masks.json +15 -0
  137. package/src/utils/defaults/touch.json +5 -0
  138. package/src/utils/glyph.ts +305 -0
  139. package/src/utils/helpers.ts +181 -0
  140. package/src/utils/locale.ts +275 -0
  141. package/src/utils/page.ts +608 -0
  142. package/src/utils/plugins/index.ts +24 -0
  143. package/src/utils/popovers.ts +216 -0
  144. package/src/utils/theme.ts +58 -0
  145. package/src/utils/touch.ts +72 -0
  146. package/src/utils/useDisplayMode.ts +198 -0
  147. 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
+ }