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,954 @@
1
+ import {
2
+ type ExtractPropTypes,
3
+ type PropType,
4
+ computed,
5
+ inject,
6
+ nextTick,
7
+ provide,
8
+ ref,
9
+ watch,
10
+ } from 'vue';
11
+ import CalendarCellPopover from '../components/CalendarGrid/CalendarCellPopover.vue';
12
+ import {
13
+ type Event,
14
+ type EventConfig,
15
+ createEvent as _createEvent,
16
+ } from '../utils/calendar/event';
17
+ import { MS_PER_HOUR, roundDate } from '../utils/date/helpers';
18
+ import { DateRange, DateRangeContext } from '../utils/date/range';
19
+ import { on } from '../utils/helpers';
20
+ import type { CalendarDay } from '../utils/page';
21
+ import { propsDef as basePropsDef, createCalendar, emitsDef } from './calendar';
22
+
23
+ type GridState =
24
+ 'NORMAL' | 'CREATE_MONITOR' | 'DRAG_MONITOR' | 'RESIZE_MONITOR';
25
+
26
+ export type GridStateEvent =
27
+ | 'GRID_CURSOR_DOWN'
28
+ | 'GRID_CURSOR_DOWN_SHIFT'
29
+ | 'GRID_CURSOR_MOVE'
30
+ | 'GRID_CURSOR_MOVE_SHIFT'
31
+ | 'GRID_CURSOR_UP'
32
+ | 'GRID_CURSOR_UP_SHIFT'
33
+ | 'EVENT_CURSOR_DOWN'
34
+ | 'EVENT_CURSOR_DOWN_SHIFT'
35
+ | 'EVENT_CURSOR_MOVE'
36
+ | 'EVENT_CURSOR_MOVE_SHIFT'
37
+ | 'EVENT_RESIZE_START_CURSOR_DOWN'
38
+ | 'EVENT_RESIZE_START_CURSOR_DOWN_SHIFT'
39
+ | 'EVENT_RESIZE_END_CURSOR_DOWN'
40
+ | 'EVENT_RESIZE_END_CURSOR_DOWN_SHIFT'
41
+ | 'ESCAPE';
42
+
43
+ export interface Point {
44
+ x: number;
45
+ y: number;
46
+ }
47
+
48
+ export interface DragOffset {
49
+ weekdays: number;
50
+ weeks: number;
51
+ ms: number;
52
+ }
53
+
54
+ export interface ResizeOffset {
55
+ weekdays: number;
56
+ weeks: number;
57
+ ms: number;
58
+ }
59
+
60
+ export interface DragOriginState {
61
+ position: number;
62
+ date: Date;
63
+ day: CalendarDay;
64
+ event: Event;
65
+ eventSelected: boolean;
66
+ ms: number;
67
+ }
68
+
69
+ export interface ResizeOriginState {
70
+ position: number;
71
+ day: CalendarDay;
72
+ event: Event;
73
+ isWeekly: boolean;
74
+ isStart: boolean;
75
+ isNew: boolean;
76
+ ms: number;
77
+ }
78
+
79
+ export interface CreateOriginState {
80
+ position: number;
81
+ date: Date;
82
+ day: CalendarDay;
83
+ isWeekly: boolean;
84
+ }
85
+
86
+ // #region Messages
87
+
88
+ type MessageType =
89
+ | 'event-create-begin'
90
+ | 'event-create-end'
91
+ | 'event-resize-begin'
92
+ | 'event-resize-update'
93
+ | 'event-resize-end'
94
+ | 'event-move-begin'
95
+ | 'event-move-update'
96
+ | 'event-move-end'
97
+ | 'event-remove';
98
+
99
+ class Messages {
100
+ constructor(private emit: Function) {}
101
+
102
+ EventCreateBegin(event: Event) {
103
+ return new CancellableEventMessage(this.emit, 'event-create-begin', event);
104
+ }
105
+
106
+ EventCreateEnd(event: Event) {
107
+ return new EventMessage(this.emit, 'event-create-end', event);
108
+ }
109
+
110
+ EventResizeBegin(event: Event) {
111
+ return new CancellableEventMessage(this.emit, 'event-resize-begin', event);
112
+ }
113
+
114
+ EventResizeUpdate(event: Event, offset: ResizeOffset) {
115
+ return new EventResizeMessage(
116
+ this.emit,
117
+ 'event-resize-update',
118
+ event,
119
+ offset,
120
+ );
121
+ }
122
+
123
+ EventResizeEnd(event: Event) {
124
+ return new EventMessage(this.emit, 'event-resize-end', event);
125
+ }
126
+
127
+ EventMoveBegin(event: Event) {
128
+ return new CancellableEventMessage(this.emit, 'event-move-begin', event);
129
+ }
130
+
131
+ EventMoveUpdate(event: Event, offset: DragOffset) {
132
+ return new EventMoveMessage(this.emit, 'event-move-update', event, offset);
133
+ }
134
+
135
+ EventMoveEnd(event: Event) {
136
+ return new EventMessage(this.emit, 'event-move-end', event);
137
+ }
138
+
139
+ EventRemove(event: Event) {
140
+ return new CancellableEventMessage(this.emit, 'event-remove', event);
141
+ }
142
+ }
143
+
144
+ class BaseMessage {
145
+ private emit: Function;
146
+ type: MessageType;
147
+
148
+ constructor(emit: Function, type: MessageType) {
149
+ this.emit = emit;
150
+ this.type = type;
151
+ }
152
+
153
+ send() {
154
+ this.emit(this.type, this);
155
+ return this;
156
+ }
157
+
158
+ async sendAsync() {
159
+ this.emit(this.type, this);
160
+ await nextTick();
161
+ return this;
162
+ }
163
+ }
164
+
165
+ class EventMessage<T extends Event | EventConfig> extends BaseMessage {
166
+ event: T;
167
+ constructor(emit: Function, type: MessageType, event: T) {
168
+ super(emit, type);
169
+ this.event = event;
170
+ }
171
+ }
172
+
173
+ class CancellableEventMessage<
174
+ T extends Event | EventConfig,
175
+ > extends EventMessage<T> {
176
+ cancel = false;
177
+ }
178
+
179
+ class EventResizeMessage extends CancellableEventMessage<Event> {
180
+ offset?: ResizeOffset;
181
+
182
+ constructor(
183
+ emit: Function,
184
+ type: MessageType,
185
+ event: Event,
186
+ offset?: ResizeOffset,
187
+ ) {
188
+ super(emit, type, event);
189
+ this.offset = offset;
190
+ }
191
+ }
192
+
193
+ class EventMoveMessage extends CancellableEventMessage<Event> {
194
+ offset?: DragOffset;
195
+
196
+ constructor(
197
+ emit: Function,
198
+ type: MessageType,
199
+ event: Event,
200
+ offset?: ResizeOffset,
201
+ ) {
202
+ super(emit, type, event);
203
+ this.offset = offset;
204
+ }
205
+ }
206
+
207
+ // #endregion Messages
208
+
209
+ export const emits = [
210
+ ...emitsDef,
211
+ 'day-header-click',
212
+ 'event-create-begin',
213
+ 'event-create-end',
214
+ 'event-resize-begin',
215
+ 'event-resize-update',
216
+ 'event-resize-end',
217
+ 'event-move-begin',
218
+ 'event-move-update',
219
+ 'event-move-end',
220
+ 'event-remove',
221
+ ];
222
+
223
+ const SNAP_MINUTES = 15;
224
+ const PIXELS_PER_HOUR = 50;
225
+ const contextKey = Symbol('__vc_grid_context__');
226
+
227
+ export const propsDef = {
228
+ ...basePropsDef,
229
+ events: {
230
+ type: Object as PropType<EventConfig[]>,
231
+ default: () => [],
232
+ },
233
+ };
234
+
235
+ export type CalendarGridProps = Readonly<ExtractPropTypes<typeof propsDef>>;
236
+
237
+ export type CalendarGridContext = ReturnType<typeof createCalendarGrid>;
238
+
239
+ type IBoundingRect = Pick<Element, 'getBoundingClientRect' | 'contains'>;
240
+
241
+ export function createCalendarGrid(
242
+ props: CalendarGridProps,
243
+ { emit, slots }: any,
244
+ ) {
245
+ const calendar = createCalendar(props, { emit, slots });
246
+ const cellPopoverRef = ref<typeof CalendarCellPopover>();
247
+ const dailyGridRef = ref<IBoundingRect | null>(null);
248
+ const weeklyGridRef = ref<IBoundingRect | null>(null);
249
+ const activeGridRef = ref<IBoundingRect | null>(null);
250
+ const messages = new Messages(emit);
251
+
252
+ const { view, isDaily, isMonthly, pages, locale, move, onDayFocusin } =
253
+ calendar;
254
+
255
+ const page = computed(() => pages.value[0]);
256
+ const days = computed(() => page.value.viewDays);
257
+ const weeks = computed(() => page.value.viewWeeks);
258
+ const dayColumns = computed(() => {
259
+ if (isDaily.value) return 1;
260
+ return weeks.value[0].days.length;
261
+ });
262
+ const dayRows = computed(() => {
263
+ if (isMonthly.value) return weeks.value.length;
264
+ return 1;
265
+ });
266
+
267
+ const snapMinutes = ref(SNAP_MINUTES);
268
+ const snapMs = computed(() => snapMinutes.value * 60 * 10000);
269
+ const pixelsPerHour = ref(PIXELS_PER_HOUR);
270
+
271
+ const state = ref<GridState>('NORMAL');
272
+ const fill = ref('light');
273
+
274
+ const eventsMap = ref<Record<any, Event>>({});
275
+ const events = computed(() => Object.values(eventsMap.value));
276
+ const detailEvent = ref<Event | null>(null);
277
+
278
+ const createOrigin = ref<CreateOriginState | null>(null);
279
+
280
+ const resizing = ref(false);
281
+ let resizeOrigin: ResizeOriginState | null = null;
282
+
283
+ const dragging = ref(false);
284
+ let dragOrigin: DragOriginState | null = null;
285
+
286
+ const isTouch = ref(false);
287
+
288
+ const active = computed(() => resizing.value || dragging.value);
289
+
290
+ const selectedEvents = computed(() => events.value.filter(e => e.selected));
291
+
292
+ const selectedEventsCount = computed(() => selectedEvents.value.length);
293
+
294
+ const hasSelectedEvents = computed(() => selectedEventsCount.value > 0);
295
+
296
+ const eventsContext = computed(() => {
297
+ const ctx = new DateRangeContext();
298
+ events.value.forEach(evt => {
299
+ ctx.render(evt, evt.range, days.value);
300
+ });
301
+ return ctx;
302
+ });
303
+
304
+ const gridStyle = computed(() => {
305
+ return {
306
+ height: `${24 * pixelsPerHour.value}px`,
307
+ };
308
+ });
309
+
310
+ function getEventContext() {
311
+ return {
312
+ locale,
313
+ days,
314
+ dayRows,
315
+ dayColumns,
316
+ isDaily,
317
+ isMonthly,
318
+ snapMinutes: snapMinutes.value,
319
+ pixelsPerHour: pixelsPerHour.value,
320
+ };
321
+ }
322
+
323
+ // #region Event details
324
+
325
+ function showCellPopover(event: Event) {
326
+ setTimeout(() => {
327
+ if (isDaily.value || !cellPopoverRef.value) return;
328
+ cellPopoverRef.value.show(event);
329
+ }, 10);
330
+ }
331
+
332
+ function updateCellPopover(event: Event) {
333
+ if (!cellPopoverRef.value) return;
334
+ cellPopoverRef.value.update(event);
335
+ }
336
+
337
+ function hideCellPopover() {
338
+ if (isDaily.value || !cellPopoverRef.value) return;
339
+ cellPopoverRef.value.hide();
340
+ }
341
+
342
+ function popoverVisible() {
343
+ return !!cellPopoverRef.value && cellPopoverRef.value.isVisible();
344
+ }
345
+
346
+ // #endregion Cell details
347
+
348
+ // #region Util
349
+
350
+ function createEventFromExisting(config: EventConfig, range?: DateRange) {
351
+ const event = events.value.find(e => e.key === config.key);
352
+ const ctx = getEventContext();
353
+ if (event != null) {
354
+ const { selected } = event;
355
+ return _createEvent(
356
+ {
357
+ ...config,
358
+ selected,
359
+ },
360
+ ctx,
361
+ );
362
+ }
363
+ return _createEvent(config, ctx);
364
+ }
365
+
366
+ function createNewEvent(date: Date, isWeekly: boolean) {
367
+ const event = _createEvent(
368
+ {
369
+ key: Symbol(),
370
+ start: date,
371
+ end: date,
372
+ allDay: isWeekly,
373
+ },
374
+ getEventContext(),
375
+ );
376
+ const msg = messages.EventCreateBegin(event).send();
377
+ if (msg.cancel || !msg.event) return;
378
+ eventsMap.value[event.key] = event;
379
+ return event;
380
+ }
381
+
382
+ function removeEvent(event: Event) {
383
+ const msg = messages.EventRemove(event).send();
384
+ if (msg.cancel) return;
385
+ delete eventsMap.value[event.key];
386
+
387
+ hideCellPopover();
388
+ }
389
+
390
+ function getEventsFromProps() {
391
+ return props.events.reduce(
392
+ (map, config) => {
393
+ map[config.key] = map[config.key] || createEventFromExisting(config);
394
+ return map;
395
+ },
396
+ {} as Record<keyof any, Event>,
397
+ );
398
+ }
399
+
400
+ function getMsFromPosition(position: number) {
401
+ const hours = Math.max(Math.min(position / pixelsPerHour.value, 24), 0);
402
+ return hours * MS_PER_HOUR;
403
+ }
404
+
405
+ function getDateFromPosition(
406
+ position: number,
407
+ day: CalendarDay,
408
+ offsetMs = 0,
409
+ snapMs = 0,
410
+ ) {
411
+ const startTime = day.startDate.getTime();
412
+ const ms = getMsFromPosition(position);
413
+ const date = roundDate(startTime + ms + offsetMs, snapMs);
414
+ return date;
415
+ }
416
+
417
+ const getPositionFromMouseEvent = (
418
+ gridEl: IBoundingRect | null,
419
+ event: MouseEvent,
420
+ ): Point => {
421
+ if (gridEl == null) return { x: 0, y: 0 };
422
+ const rect = gridEl.getBoundingClientRect();
423
+ const x = event.clientX - rect.left;
424
+ const y = event.clientY - rect.top;
425
+ return { x, y };
426
+ };
427
+
428
+ const getPositionFromTouchEvent = (
429
+ gridEl: IBoundingRect | null,
430
+ event: TouchEvent,
431
+ ): Point => {
432
+ if (gridEl == null) return { x: 0, y: 0 };
433
+ const rect = gridEl.getBoundingClientRect();
434
+ const touch = event.targetTouches[0] || event.changedTouches[0];
435
+ const x = touch.clientX - rect.left;
436
+ const y = touch.clientY - rect.top;
437
+ return { x, y };
438
+ };
439
+
440
+ const getPositionFromUIEvent = (
441
+ gridEl: IBoundingRect | null,
442
+ event: UIEvent,
443
+ ): Point => {
444
+ if (event.type.startsWith('touch'))
445
+ return getPositionFromTouchEvent(gridEl, event as TouchEvent);
446
+ return getPositionFromMouseEvent(gridEl, event as MouseEvent);
447
+ };
448
+
449
+ const getDayFromPosition = (el: IBoundingRect | null, { x, y }: any) => {
450
+ if (el == null) return days.value[0];
451
+ const rect = el.getBoundingClientRect();
452
+ const dayWidth = rect.width / dayColumns.value;
453
+ const dayHeight = rect.height / dayRows.value;
454
+ const xNorm = Math.max(Math.min(x, rect.width), 0);
455
+ const yNorm = Math.max(Math.min(y, rect.height), 0);
456
+ const xIdx = Math.min(Math.floor(xNorm / dayWidth), dayColumns.value - 1);
457
+ const yIdx = Math.min(Math.floor(yNorm / dayHeight), dayRows.value - 1);
458
+ const idx = xIdx + yIdx * dayColumns.value;
459
+ return days.value[idx];
460
+ };
461
+
462
+ // #endregion Util
463
+
464
+ // #region Cell Operations
465
+
466
+ function forSelectedEvents(fn: (event: Event) => void) {
467
+ selectedEvents.value.forEach(e => fn(e));
468
+ }
469
+
470
+ function deselectAllEvents() {
471
+ forSelectedEvents(cell => (cell.selected = false));
472
+ }
473
+
474
+ // #endregion Cell Operations
475
+
476
+ // #region Resizing
477
+
478
+ function startResizingEvents(
479
+ position: number,
480
+ day: CalendarDay,
481
+ event: Event,
482
+ isStart: boolean,
483
+ isNew: boolean,
484
+ ) {
485
+ if (active.value) return;
486
+ resizing.value = true;
487
+ event.selected = true;
488
+ const isWeekly = activeGridRef.value === weeklyGridRef.value;
489
+ const ms = getMsFromPosition(position);
490
+ resizeOrigin = {
491
+ position,
492
+ day,
493
+ event,
494
+ isWeekly,
495
+ isStart,
496
+ isNew,
497
+ ms,
498
+ };
499
+ forSelectedEvents(event => {
500
+ const msg = messages.EventResizeBegin(event).send();
501
+ if (msg.cancel) return;
502
+ event.startResize(day, isStart);
503
+ });
504
+ }
505
+
506
+ function updateResizingEvents(position: number, day: CalendarDay) {
507
+ if (!resizing.value || !resizeOrigin) return;
508
+ const offset: ResizeOffset = { weeks: 0, weekdays: 0, ms: 0 };
509
+ if (resizeOrigin.isWeekly) {
510
+ offset.weeks = day.weekPosition - resizeOrigin.day.weekPosition;
511
+ offset.weekdays = day.weekdayPosition - resizeOrigin.day.weekdayPosition;
512
+ } else {
513
+ offset.ms = getMsFromPosition(position) - resizeOrigin.ms;
514
+ }
515
+ forSelectedEvents(event => {
516
+ const msg = messages.EventResizeUpdate(event, offset).send();
517
+ if (msg.cancel) return;
518
+ event.updateResize(offset);
519
+ });
520
+ }
521
+
522
+ function stopResizingEvents() {
523
+ if (!resizing.value || !resizeOrigin) return;
524
+ forSelectedEvents(event => {
525
+ messages.EventResizeEnd(event);
526
+ if (resizeOrigin!.isNew && event === resizeOrigin!.event) {
527
+ messages.EventCreateEnd(event).send();
528
+ showCellPopover(event);
529
+ }
530
+ event.stopResize();
531
+ });
532
+ resizing.value = false;
533
+ resizeOrigin = null;
534
+ }
535
+
536
+ // #endregion Resizing
537
+
538
+ // #region Dragging
539
+
540
+ function startDraggingEvents(
541
+ position: number,
542
+ day: CalendarDay,
543
+ event: Event,
544
+ ) {
545
+ if (active.value) return;
546
+ dragging.value = true;
547
+ const date = getDateFromPosition(position, day, 0, 0);
548
+ const eventSelected = event.selected;
549
+ event.selected = true;
550
+ const ms = getMsFromPosition(position);
551
+ dragOrigin = {
552
+ position,
553
+ date,
554
+ day,
555
+ event,
556
+ eventSelected,
557
+ ms,
558
+ };
559
+ selectedEvents.value.forEach(event => {
560
+ const msg = messages.EventMoveBegin(event).send();
561
+ if (msg.cancel) return;
562
+ event.startDrag(day);
563
+ });
564
+ }
565
+
566
+ function updateDraggingEvents(position: number, day: CalendarDay) {
567
+ if (!dragging.value || !dragOrigin) return;
568
+ const offset = {
569
+ weeks: day.weekPosition - dragOrigin.day.weekPosition,
570
+ weekdays: day.weekdayPosition - dragOrigin.day.weekdayPosition,
571
+ ms: getMsFromPosition(position) - dragOrigin.ms,
572
+ };
573
+ forSelectedEvents(event => {
574
+ const msg = messages.EventMoveUpdate(event, offset).send();
575
+ if (msg.cancel) return;
576
+ event.updateDrag(offset);
577
+ });
578
+ }
579
+
580
+ function stopDraggingEvents() {
581
+ if (!dragging.value || !dragOrigin) return;
582
+ dragging.value = false;
583
+ dragOrigin = null;
584
+ forSelectedEvents(event => {
585
+ messages.EventMoveEnd(event).send();
586
+ event.stopDrag();
587
+ });
588
+ }
589
+
590
+ // #endregion Dragging
591
+
592
+ // #region Watchers
593
+
594
+ function refreshEventsFromProps() {
595
+ eventsMap.value = getEventsFromProps();
596
+ }
597
+
598
+ watch(
599
+ () => props.events,
600
+ () => {
601
+ refreshEventsFromProps();
602
+ },
603
+ {
604
+ deep: true,
605
+ },
606
+ );
607
+
608
+ watch([view], () => {
609
+ deselectAllEvents();
610
+ });
611
+
612
+ // #endregion Watchers
613
+
614
+ // #region State management
615
+
616
+ function handleNormalEvent(
617
+ gse: GridStateEvent,
618
+ day: CalendarDay,
619
+ position: number,
620
+ evt: Event | undefined,
621
+ ) {
622
+ switch (gse) {
623
+ case 'GRID_CURSOR_DOWN':
624
+ case 'GRID_CURSOR_DOWN_SHIFT': {
625
+ createOrigin.value = {
626
+ isWeekly: activeGridRef.value === weeklyGridRef.value,
627
+ date: getDateFromPosition(position, day),
628
+ position,
629
+ day,
630
+ };
631
+ state.value = 'CREATE_MONITOR';
632
+ break;
633
+ }
634
+ case 'EVENT_CURSOR_DOWN': {
635
+ if (!evt) return;
636
+ if (!evt.selected) deselectAllEvents();
637
+ startDraggingEvents(position, day, evt);
638
+ state.value = 'DRAG_MONITOR';
639
+ break;
640
+ }
641
+ case 'EVENT_CURSOR_DOWN_SHIFT': {
642
+ if (!evt) return;
643
+ startDraggingEvents(position, day, evt);
644
+ state.value = 'DRAG_MONITOR';
645
+ break;
646
+ }
647
+ case 'EVENT_RESIZE_START_CURSOR_DOWN': {
648
+ if (!evt) return;
649
+ if (!evt.selected) deselectAllEvents();
650
+ startResizingEvents(position, day, evt, true, false);
651
+ state.value = 'RESIZE_MONITOR';
652
+ break;
653
+ }
654
+ case 'EVENT_RESIZE_START_CURSOR_DOWN_SHIFT': {
655
+ if (!evt) return;
656
+ startResizingEvents(position, day, evt, true, false);
657
+ state.value = 'RESIZE_MONITOR';
658
+ break;
659
+ }
660
+ case 'EVENT_RESIZE_END_CURSOR_DOWN': {
661
+ if (!evt) return;
662
+ if (!evt.selected) deselectAllEvents();
663
+ startResizingEvents(position, day, evt, false, false);
664
+ state.value = 'RESIZE_MONITOR';
665
+ break;
666
+ }
667
+ case 'EVENT_RESIZE_END_CURSOR_DOWN_SHIFT': {
668
+ if (!evt) return;
669
+ startResizingEvents(position, day, evt, false, false);
670
+ state.value = 'RESIZE_MONITOR';
671
+ break;
672
+ }
673
+ }
674
+ }
675
+
676
+ function handleCreateMonitorEvent(gse: GridStateEvent, day: CalendarDay) {
677
+ if (!createOrigin.value) return;
678
+
679
+ switch (gse) {
680
+ case 'ESCAPE': {
681
+ deselectAllEvents();
682
+ break;
683
+ }
684
+ case 'GRID_CURSOR_UP':
685
+ case 'GRID_CURSOR_UP_SHIFT': {
686
+ deselectAllEvents();
687
+ if (!popoverVisible()) {
688
+ const { position, isWeekly } = createOrigin.value;
689
+ const date = getDateFromPosition(position, day);
690
+ const evt = createNewEvent(date, isWeekly);
691
+ if (evt) {
692
+ evt.selected = true;
693
+ messages.EventCreateEnd(evt).send();
694
+ showCellPopover(evt);
695
+ }
696
+ }
697
+ state.value = 'NORMAL';
698
+ break;
699
+ }
700
+ case 'EVENT_CURSOR_MOVE':
701
+ case 'EVENT_CURSOR_MOVE_SHIFT':
702
+ case 'GRID_CURSOR_MOVE':
703
+ case 'GRID_CURSOR_MOVE_SHIFT': {
704
+ // if (isTouch.value || isMonthly.value) {
705
+ // if (isTouch.value) {
706
+ // state.value = 'NORMAL';
707
+ // return;
708
+ // }
709
+ deselectAllEvents();
710
+ const { position, isWeekly } = createOrigin.value;
711
+ const date = getDateFromPosition(position, day);
712
+ const evt = createNewEvent(date, isWeekly);
713
+ if (evt) {
714
+ startResizingEvents(position, day, evt, false, true);
715
+ updateResizingEvents(position, day);
716
+ state.value = 'RESIZE_MONITOR';
717
+ }
718
+ break;
719
+ }
720
+ }
721
+ }
722
+
723
+ function handleResizeMonitorEvent(
724
+ event: GridStateEvent,
725
+ position: number,
726
+ day: CalendarDay,
727
+ ) {
728
+ if (!resizeOrigin) return;
729
+ switch (event) {
730
+ case 'EVENT_CURSOR_MOVE':
731
+ case 'EVENT_CURSOR_MOVE_SHIFT':
732
+ case 'GRID_CURSOR_MOVE':
733
+ case 'GRID_CURSOR_MOVE_SHIFT': {
734
+ updateResizingEvents(position, day);
735
+ if (!resizeOrigin.isNew) {
736
+ updateCellPopover(resizeOrigin.event);
737
+ }
738
+ break;
739
+ }
740
+ case 'GRID_CURSOR_UP': {
741
+ if (position === resizeOrigin.position) {
742
+ deselectAllEvents();
743
+ resizeOrigin.event.selected = true;
744
+ }
745
+ stopResizingEvents();
746
+ state.value = 'NORMAL';
747
+ break;
748
+ }
749
+ case 'GRID_CURSOR_UP_SHIFT': {
750
+ stopResizingEvents();
751
+ state.value = 'NORMAL';
752
+ break;
753
+ }
754
+ }
755
+ }
756
+
757
+ function handleDragMonitorEvent(
758
+ event: GridStateEvent,
759
+ day: CalendarDay,
760
+ position: number,
761
+ ) {
762
+ if (!dragOrigin) return;
763
+ switch (event) {
764
+ case 'GRID_CURSOR_MOVE':
765
+ case 'GRID_CURSOR_MOVE_SHIFT': {
766
+ updateDraggingEvents(position, day);
767
+ updateCellPopover(dragOrigin.event);
768
+ break;
769
+ }
770
+ case 'GRID_CURSOR_UP': {
771
+ const origin = dragOrigin;
772
+ stopDraggingEvents();
773
+ if (position === origin.position) {
774
+ deselectAllEvents();
775
+ origin.event.selected = true;
776
+ showCellPopover(origin.event);
777
+ }
778
+ state.value = 'NORMAL';
779
+ break;
780
+ }
781
+ case 'GRID_CURSOR_UP_SHIFT': {
782
+ stopDraggingEvents();
783
+ state.value = 'NORMAL';
784
+ break;
785
+ }
786
+ }
787
+ }
788
+
789
+ function updateState(
790
+ gse: GridStateEvent,
791
+ day: CalendarDay,
792
+ position: number,
793
+ evt: Event | undefined = undefined,
794
+ ) {
795
+ switch (state.value) {
796
+ case 'NORMAL': {
797
+ handleNormalEvent(gse, day, position, evt);
798
+ break;
799
+ }
800
+ case 'CREATE_MONITOR': {
801
+ handleCreateMonitorEvent(gse, day);
802
+ break;
803
+ }
804
+ case 'RESIZE_MONITOR': {
805
+ handleResizeMonitorEvent(gse, position, day);
806
+ break;
807
+ }
808
+ case 'DRAG_MONITOR': {
809
+ handleDragMonitorEvent(gse, day, position);
810
+ break;
811
+ }
812
+ }
813
+ }
814
+
815
+ const setActiveGrid = (event: MouseEvent | TouchEvent) => {
816
+ activeGridRef.value =
817
+ [dailyGridRef, weeklyGridRef].find(
818
+ ref => ref.value && ref.value.contains(event.currentTarget as Node),
819
+ )?.value ?? null;
820
+ };
821
+
822
+ const handleEvent = (
823
+ stateEvent: GridStateEvent,
824
+ event: MouseEvent | TouchEvent | KeyboardEvent,
825
+ evt: Event | undefined = undefined,
826
+ ) => {
827
+ if (!activeGridRef.value) return;
828
+ if (event.type.startsWith('touch')) {
829
+ isTouch.value = true;
830
+ } else if (isTouch.value) {
831
+ return;
832
+ }
833
+ const eventName = (
834
+ event.shiftKey ? `${stateEvent}_SHIFT` : stateEvent
835
+ ) as GridStateEvent;
836
+ const position = getPositionFromUIEvent(activeGridRef.value, event);
837
+ const day = getDayFromPosition(activeGridRef.value, position);
838
+ updateState(eventName, day, position.y, evt);
839
+ if (stateEvent === 'GRID_CURSOR_DOWN') {
840
+ onDayFocusin(day, null);
841
+ }
842
+ };
843
+
844
+ const startMonitoringGridMove = () => {
845
+ const offMove = on(window, 'mousemove', event => {
846
+ handleEvent('GRID_CURSOR_MOVE', event as MouseEvent);
847
+ });
848
+ const offUp = on(window, 'mouseup', event => {
849
+ handleEvent('GRID_CURSOR_UP', event as MouseEvent);
850
+ offMove();
851
+ offUp();
852
+ });
853
+ };
854
+
855
+ // #endregion State management
856
+
857
+ refreshEventsFromProps();
858
+
859
+ const context = {
860
+ ...calendar,
861
+ dailyGridRef,
862
+ weeklyGridRef,
863
+ cellPopoverRef,
864
+ dayColumns,
865
+ dayRows,
866
+ snapMinutes,
867
+ snapMs,
868
+ pixelsPerHour,
869
+ isTouch,
870
+ events,
871
+ eventsMap,
872
+ selectedEvents,
873
+ hasSelectedEvents,
874
+ eventsContext,
875
+ detailEvent,
876
+ resizing,
877
+ dragging,
878
+ gridStyle,
879
+ fill,
880
+ page,
881
+ days,
882
+ weeks,
883
+ // Methods
884
+ removeEvent,
885
+ // Event handlers
886
+ onDayNumberClick(day: CalendarDay) {
887
+ emit('day-header-click', day);
888
+ move(day, { view: 'daily' });
889
+ },
890
+ onGridEscapeKeydown() {
891
+ updateState('ESCAPE', days.value[0], 0);
892
+ },
893
+ // Mouse event handlers
894
+ onGridMouseDown(event: MouseEvent) {
895
+ setActiveGrid(event);
896
+ handleEvent('GRID_CURSOR_DOWN', event);
897
+ startMonitoringGridMove();
898
+ },
899
+ onEventMouseDown(event: MouseEvent, evt: Event) {
900
+ setActiveGrid(event);
901
+ handleEvent('EVENT_CURSOR_DOWN', event, evt);
902
+ },
903
+ onEventResizeStartMouseDown(event: MouseEvent, evt: Event) {
904
+ setActiveGrid(event);
905
+ handleEvent('EVENT_RESIZE_START_CURSOR_DOWN', event, evt);
906
+ },
907
+ onEventResizeEndMouseDown(event: MouseEvent, evt: Event) {
908
+ setActiveGrid(event);
909
+ handleEvent('EVENT_RESIZE_END_CURSOR_DOWN', event, evt);
910
+ },
911
+ // Touch event handlers
912
+ onGridTouchStart(event: TouchEvent) {
913
+ setActiveGrid(event);
914
+ handleEvent('GRID_CURSOR_DOWN', event);
915
+ },
916
+ onGridTouchMove(event: TouchEvent) {
917
+ handleEvent('GRID_CURSOR_MOVE', event);
918
+ },
919
+ onGridTouchEnd(event: TouchEvent) {
920
+ handleEvent('GRID_CURSOR_UP', event);
921
+ },
922
+ onEventTouchStart(event: TouchEvent, evt: Event) {
923
+ setActiveGrid(event);
924
+ handleEvent('EVENT_CURSOR_DOWN', event, evt);
925
+ },
926
+ onEventTouchMove(event: TouchEvent, evt: Event) {
927
+ handleEvent('GRID_CURSOR_MOVE', event, evt);
928
+ },
929
+ onEventTouchEnd(event: TouchEvent, evt: Event) {
930
+ handleEvent('GRID_CURSOR_UP', event, evt);
931
+ },
932
+ onEventResizeStartTouchStart(event: TouchEvent, evt: Event) {
933
+ setActiveGrid(event);
934
+ handleEvent('EVENT_RESIZE_START_CURSOR_DOWN', event, evt);
935
+ },
936
+ onEventResizeEndTouchStart(event: TouchEvent, evt: Event) {
937
+ setActiveGrid(event);
938
+ handleEvent('EVENT_RESIZE_END_CURSOR_DOWN', event, evt);
939
+ },
940
+ };
941
+ provide(contextKey, context);
942
+
943
+ return context;
944
+ }
945
+
946
+ export function useCalendarGrid() {
947
+ const context = inject<CalendarGridContext>(contextKey);
948
+ if (!context) {
949
+ throw new Error(
950
+ 'Calendar context missing. Please verify this component is nested within a valid context provider.',
951
+ );
952
+ }
953
+ return context;
954
+ }