nuxt-ui-elements-pro 0.1.5 → 0.1.7
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/dist/module.json +1 -1
- package/dist/module.mjs +153 -18
- package/dist/runtime/components/EventCalendar.d.vue.ts +109 -22
- package/dist/runtime/components/EventCalendar.vue +225 -602
- package/dist/runtime/components/EventCalendar.vue.d.ts +109 -22
- package/dist/runtime/components/OrgChart.d.vue.ts +191 -0
- package/dist/runtime/components/OrgChart.vue +290 -0
- package/dist/runtime/components/OrgChart.vue.d.ts +191 -0
- package/dist/runtime/components/event-calendar/EventCalendarHeader.d.vue.ts +45 -0
- package/dist/runtime/components/event-calendar/EventCalendarHeader.vue +55 -0
- package/dist/runtime/components/event-calendar/EventCalendarHeader.vue.d.ts +45 -0
- package/dist/runtime/components/event-calendar/EventCalendarListView.d.vue.ts +25 -0
- package/dist/runtime/components/event-calendar/EventCalendarListView.vue +95 -0
- package/dist/runtime/components/event-calendar/EventCalendarListView.vue.d.ts +25 -0
- package/dist/runtime/components/event-calendar/EventCalendarMonthView.d.vue.ts +34 -0
- package/dist/runtime/components/event-calendar/EventCalendarMonthView.vue +336 -0
- package/dist/runtime/components/event-calendar/EventCalendarMonthView.vue.d.ts +34 -0
- package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.d.vue.ts +31 -0
- package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.vue +306 -0
- package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.vue.d.ts +31 -0
- package/dist/runtime/components/org-chart/OrgChartConnectors.d.vue.ts +15 -0
- package/dist/runtime/components/org-chart/OrgChartConnectors.vue +29 -0
- package/dist/runtime/components/org-chart/OrgChartConnectors.vue.d.ts +15 -0
- package/dist/runtime/components/org-chart/OrgChartControls.d.vue.ts +19 -0
- package/dist/runtime/components/org-chart/OrgChartControls.vue +25 -0
- package/dist/runtime/components/org-chart/OrgChartControls.vue.d.ts +19 -0
- package/dist/runtime/components/org-chart/OrgChartNode.d.vue.ts +30 -0
- package/dist/runtime/components/org-chart/OrgChartNode.vue +129 -0
- package/dist/runtime/components/org-chart/OrgChartNode.vue.d.ts +30 -0
- package/dist/runtime/composables/useEventCalendar.d.ts +52 -0
- package/dist/runtime/composables/useEventCalendar.js +362 -0
- package/dist/runtime/composables/useEventCalendarContext.d.ts +8 -0
- package/dist/runtime/composables/useEventCalendarContext.js +11 -0
- package/dist/runtime/composables/useEventCalendarDragDrop.d.ts +1 -1
- package/dist/runtime/composables/useEventCalendarDragDrop.js +11 -9
- package/dist/runtime/composables/useEventCalendarKeyboard.d.ts +20 -0
- package/dist/runtime/composables/useEventCalendarKeyboard.js +128 -0
- package/dist/runtime/composables/useEventCalendarResize.d.ts +31 -0
- package/dist/runtime/composables/useEventCalendarResize.js +87 -0
- package/dist/runtime/composables/useEventCalendarSelect.d.ts +21 -0
- package/dist/runtime/composables/useEventCalendarSelect.js +119 -0
- package/dist/runtime/composables/useOrgChart.d.ts +42 -0
- package/dist/runtime/composables/useOrgChart.js +154 -0
- package/dist/runtime/composables/useOrgChartContext.d.ts +8 -0
- package/dist/runtime/composables/useOrgChartContext.js +11 -0
- package/dist/runtime/composables/useOrgChartDragDrop.d.ts +20 -0
- package/dist/runtime/composables/useOrgChartDragDrop.js +67 -0
- package/dist/runtime/composables/useOrgChartKeyboard.d.ts +16 -0
- package/dist/runtime/composables/useOrgChartKeyboard.js +101 -0
- package/dist/runtime/composables/useOrgChartSearch.d.ts +16 -0
- package/dist/runtime/composables/useOrgChartSearch.js +62 -0
- package/dist/runtime/composables/useOrgChartZoom.d.ts +26 -0
- package/dist/runtime/composables/useOrgChartZoom.js +101 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/types/event-calendar.d.ts +170 -0
- package/dist/runtime/types/index.d.ts +2 -0
- package/dist/runtime/types/index.js +2 -0
- package/dist/runtime/types/org-chart.d.ts +181 -0
- package/dist/runtime/types/org-chart.js +0 -0
- package/dist/runtime/utils/event-calendar.d.ts +22 -1
- package/dist/runtime/utils/event-calendar.js +199 -1
- package/dist/runtime/utils/org-chart.d.ts +55 -0
- package/dist/runtime/utils/org-chart.js +367 -0
- package/dist/runtime/utils/recurrence.d.ts +30 -0
- package/dist/runtime/utils/recurrence.js +150 -0
- package/package.json +15 -6
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CalendarDate, CalendarDateTime, DateValue } from "@internationalized/date";
|
|
2
|
+
import type { CalendarEvent, CalendarDay, CalendarView, CalendarWeek, NormalizedEvent, TimeSlot, TimeGridDay, MonthViewOptions, WeekViewOptions, DayViewOptions, MonthWeekLayout, AllDayLayout } from "../types/event-calendar.js";
|
|
3
|
+
export declare const SLOT_HEIGHT = 48;
|
|
4
|
+
export interface UseEventCalendarOptions {
|
|
5
|
+
events: () => CalendarEvent[];
|
|
6
|
+
modelValue: () => Date | string | DateValue | undefined;
|
|
7
|
+
view: () => CalendarView;
|
|
8
|
+
locale: () => string;
|
|
9
|
+
weekStartsOn: () => 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
10
|
+
monthOptions: () => MonthViewOptions | undefined;
|
|
11
|
+
weekOptions: () => WeekViewOptions | undefined;
|
|
12
|
+
dayOptions: () => DayViewOptions | undefined;
|
|
13
|
+
onUpdateModelValue: (date: CalendarDate) => void;
|
|
14
|
+
onUpdateView: (view: CalendarView) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function useEventCalendar(options: UseEventCalendarOptions): {
|
|
17
|
+
displayDate: import("vue").ComputedRef<CalendarDate>;
|
|
18
|
+
monthConfig: import("vue").ComputedRef<{
|
|
19
|
+
maxEvents: number;
|
|
20
|
+
fixedWeeks: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
weekConfig: import("vue").ComputedRef<{
|
|
23
|
+
startHour: number;
|
|
24
|
+
endHour: number;
|
|
25
|
+
slotDuration: 15 | 30 | 60;
|
|
26
|
+
}>;
|
|
27
|
+
dayConfig: import("vue").ComputedRef<{
|
|
28
|
+
startHour: number;
|
|
29
|
+
endHour: number;
|
|
30
|
+
slotDuration: 15 | 30 | 60;
|
|
31
|
+
}>;
|
|
32
|
+
monthWeeks: import("vue").ComputedRef<CalendarWeek[]>;
|
|
33
|
+
monthWeekLayouts: import("vue").ComputedRef<MonthWeekLayout[]>;
|
|
34
|
+
weekdayLabels: import("vue").ComputedRef<string[]>;
|
|
35
|
+
weekDays: import("vue").ComputedRef<CalendarDay[]>;
|
|
36
|
+
dayViewDate: import("vue").ComputedRef<CalendarDay>;
|
|
37
|
+
listDays: import("vue").ComputedRef<CalendarDay[]>;
|
|
38
|
+
timeGridDays: import("vue").ComputedRef<TimeGridDay[]>;
|
|
39
|
+
allDayLayout: import("vue").ComputedRef<AllDayLayout>;
|
|
40
|
+
timeGridSlots: import("vue").ComputedRef<TimeSlot[]>;
|
|
41
|
+
headerTitle: import("vue").ComputedRef<string>;
|
|
42
|
+
formatTime: (d: CalendarDate | CalendarDateTime) => string;
|
|
43
|
+
formatTimeRange: (start: CalendarDate | CalendarDateTime, end: CalendarDate | CalendarDateTime) => string;
|
|
44
|
+
getEventStyle: (event: NormalizedEvent, bgOpacity?: number) => Record<string, string>;
|
|
45
|
+
isEventStart: (event: NormalizedEvent, date: CalendarDate) => boolean;
|
|
46
|
+
goToPrev: () => void;
|
|
47
|
+
goToNext: () => void;
|
|
48
|
+
goToToday: () => void;
|
|
49
|
+
setView: (v: CalendarView) => void;
|
|
50
|
+
normalizedEvents: import("vue").ComputedRef<NormalizedEvent[]>;
|
|
51
|
+
SLOT_HEIGHT: number;
|
|
52
|
+
};
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { toCalendarDate } from "@internationalized/date";
|
|
2
|
+
import { computed, shallowRef, watchEffect } from "vue";
|
|
3
|
+
import { createMonth } from "reka-ui/date";
|
|
4
|
+
import {
|
|
5
|
+
today,
|
|
6
|
+
startOf,
|
|
7
|
+
add,
|
|
8
|
+
subtract,
|
|
9
|
+
format,
|
|
10
|
+
isSameMonth,
|
|
11
|
+
isToday as checkIsToday,
|
|
12
|
+
isWeekend as checkIsWeekend,
|
|
13
|
+
asCalendarDate,
|
|
14
|
+
asCalendarDateTime,
|
|
15
|
+
getLocalTimeZone
|
|
16
|
+
} from "#std/date";
|
|
17
|
+
import { hasTimeComponent, layoutTimedEvents, layoutMonthWeekEvents, layoutAllDayEvents, startOfWeekByDay, endOfWeekByDay } from "../utils/event-calendar.js";
|
|
18
|
+
import { getVisibleRange, expandRecurringEvents } from "../utils/recurrence.js";
|
|
19
|
+
export const SLOT_HEIGHT = 48;
|
|
20
|
+
export function useEventCalendar(options) {
|
|
21
|
+
const monthConfig = computed(() => ({
|
|
22
|
+
maxEvents: options.monthOptions()?.maxEvents ?? 3,
|
|
23
|
+
fixedWeeks: options.monthOptions()?.fixedWeeks ?? true
|
|
24
|
+
}));
|
|
25
|
+
const weekConfig = computed(() => ({
|
|
26
|
+
startHour: options.weekOptions()?.startHour ?? 7,
|
|
27
|
+
endHour: options.weekOptions()?.endHour ?? 22,
|
|
28
|
+
slotDuration: options.weekOptions()?.slotDuration ?? 30
|
|
29
|
+
}));
|
|
30
|
+
const dayConfig = computed(() => ({
|
|
31
|
+
startHour: options.dayOptions()?.startHour ?? 7,
|
|
32
|
+
endHour: options.dayOptions()?.endHour ?? 22,
|
|
33
|
+
slotDuration: options.dayOptions()?.slotDuration ?? 30
|
|
34
|
+
}));
|
|
35
|
+
const displayDate = computed(() => {
|
|
36
|
+
const mv = options.modelValue();
|
|
37
|
+
if (mv) return asCalendarDate(mv);
|
|
38
|
+
return today();
|
|
39
|
+
});
|
|
40
|
+
const rruleModule = shallowRef(null);
|
|
41
|
+
watchEffect(() => {
|
|
42
|
+
if (rruleModule.value || !options.events().some((e) => e.rrule)) return;
|
|
43
|
+
import("rrule").then((mod) => {
|
|
44
|
+
rruleModule.value = mod;
|
|
45
|
+
}).catch(() => {
|
|
46
|
+
console.warn('[UEEventCalendar] Install "rrule" for recurring events: pnpm add rrule');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
const visibleRange = computed(
|
|
50
|
+
() => getVisibleRange(displayDate.value, options.view(), options.weekStartsOn(), monthConfig.value.fixedWeeks)
|
|
51
|
+
);
|
|
52
|
+
const expandedEvents = computed(() => {
|
|
53
|
+
const raw = options.events();
|
|
54
|
+
const range = visibleRange.value;
|
|
55
|
+
const rrule = rruleModule.value;
|
|
56
|
+
if (!rrule) return raw;
|
|
57
|
+
return expandRecurringEvents(raw, range, rrule);
|
|
58
|
+
});
|
|
59
|
+
function goToPrev() {
|
|
60
|
+
const v = options.view();
|
|
61
|
+
let target;
|
|
62
|
+
if (v === "month") {
|
|
63
|
+
target = startOf(subtract(displayDate.value, 1, "month"), "month");
|
|
64
|
+
} else if (v === "week" || v === "list") {
|
|
65
|
+
target = asCalendarDate(subtract(displayDate.value, 1, "week"));
|
|
66
|
+
} else {
|
|
67
|
+
target = asCalendarDate(subtract(displayDate.value, 1, "day"));
|
|
68
|
+
}
|
|
69
|
+
options.onUpdateModelValue(target);
|
|
70
|
+
}
|
|
71
|
+
function goToNext() {
|
|
72
|
+
const v = options.view();
|
|
73
|
+
let target;
|
|
74
|
+
if (v === "month") {
|
|
75
|
+
target = startOf(add(displayDate.value, 1, "month"), "month");
|
|
76
|
+
} else if (v === "week" || v === "list") {
|
|
77
|
+
target = asCalendarDate(add(displayDate.value, 1, "week"));
|
|
78
|
+
} else {
|
|
79
|
+
target = asCalendarDate(add(displayDate.value, 1, "day"));
|
|
80
|
+
}
|
|
81
|
+
options.onUpdateModelValue(target);
|
|
82
|
+
}
|
|
83
|
+
function goToToday() {
|
|
84
|
+
options.onUpdateModelValue(today());
|
|
85
|
+
}
|
|
86
|
+
function setView(v) {
|
|
87
|
+
options.onUpdateView(v);
|
|
88
|
+
}
|
|
89
|
+
const headerTitle = computed(() => {
|
|
90
|
+
const v = options.view();
|
|
91
|
+
const loc = options.locale();
|
|
92
|
+
if (v === "month") {
|
|
93
|
+
return format(displayDate.value, "MMMM YYYY", loc);
|
|
94
|
+
}
|
|
95
|
+
if (v === "week" || v === "list") {
|
|
96
|
+
const weekStart = startOfWeekByDay(asCalendarDate(displayDate.value), options.weekStartsOn());
|
|
97
|
+
const weekEnd = endOfWeekByDay(asCalendarDate(displayDate.value), options.weekStartsOn());
|
|
98
|
+
if (weekStart.month === weekEnd.month) {
|
|
99
|
+
return `${format(weekStart, "MMM D", loc)} \u2013 ${format(weekEnd, "D, YYYY", loc)}`;
|
|
100
|
+
}
|
|
101
|
+
return `${format(weekStart, "MMM D", loc)} \u2013 ${format(weekEnd, "MMM D, YYYY", loc)}`;
|
|
102
|
+
}
|
|
103
|
+
return format(displayDate.value, "dddd, MMMM D, YYYY", loc);
|
|
104
|
+
});
|
|
105
|
+
function parseDateInput(input) {
|
|
106
|
+
if (typeof input === "object" && "calendar" in input) {
|
|
107
|
+
return input;
|
|
108
|
+
}
|
|
109
|
+
if (input instanceof Date) {
|
|
110
|
+
const iso = input.toISOString();
|
|
111
|
+
if (iso.endsWith("T00:00:00.000Z")) {
|
|
112
|
+
return asCalendarDate(input);
|
|
113
|
+
}
|
|
114
|
+
return asCalendarDateTime(input);
|
|
115
|
+
}
|
|
116
|
+
if (typeof input === "string") {
|
|
117
|
+
if (input.includes("T") || input.includes(" ")) {
|
|
118
|
+
try {
|
|
119
|
+
return asCalendarDateTime(input);
|
|
120
|
+
} catch {
|
|
121
|
+
return asCalendarDate(input);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return asCalendarDate(input);
|
|
125
|
+
}
|
|
126
|
+
return today();
|
|
127
|
+
}
|
|
128
|
+
const normalizedEvents = computed(() => {
|
|
129
|
+
return expandedEvents.value.map((event) => {
|
|
130
|
+
const start = parseDateInput(event.start);
|
|
131
|
+
const end = event.end ? parseDateInput(event.end) : start;
|
|
132
|
+
const allDay = event.allDay ?? !hasTimeComponent(start);
|
|
133
|
+
return {
|
|
134
|
+
id: event.id,
|
|
135
|
+
title: event.title,
|
|
136
|
+
start,
|
|
137
|
+
end,
|
|
138
|
+
color: event.color ?? "primary",
|
|
139
|
+
allDay,
|
|
140
|
+
draggable: event.draggable ?? true,
|
|
141
|
+
resizable: event.resizable ?? true,
|
|
142
|
+
original: event,
|
|
143
|
+
recurringEventId: event.recurringEventId
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
function isAllDayOrMultiDay(event) {
|
|
148
|
+
if (event.allDay) return true;
|
|
149
|
+
const startDate = toCalendarDate(event.start);
|
|
150
|
+
const endDate = toCalendarDate(event.end);
|
|
151
|
+
return startDate.compare(endDate) !== 0;
|
|
152
|
+
}
|
|
153
|
+
const eventsByDate = computed(() => {
|
|
154
|
+
const map = /* @__PURE__ */ new Map();
|
|
155
|
+
for (const event of normalizedEvents.value) {
|
|
156
|
+
const startDate = toCalendarDate(event.start);
|
|
157
|
+
const endDate = toCalendarDate(event.end);
|
|
158
|
+
let current = startDate;
|
|
159
|
+
while (current.compare(endDate) <= 0) {
|
|
160
|
+
const key = current.toString();
|
|
161
|
+
const existing = map.get(key) ?? [];
|
|
162
|
+
existing.push(event);
|
|
163
|
+
map.set(key, existing);
|
|
164
|
+
current = asCalendarDate(add(current, 1, "day"));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const events of map.values()) {
|
|
168
|
+
events.sort((a, b) => {
|
|
169
|
+
const aFirst = isAllDayOrMultiDay(a);
|
|
170
|
+
const bFirst = isAllDayOrMultiDay(b);
|
|
171
|
+
if (aFirst && !bFirst) return -1;
|
|
172
|
+
if (!aFirst && bFirst) return 1;
|
|
173
|
+
if (!aFirst && !bFirst && hasTimeComponent(a.start) && hasTimeComponent(b.start)) {
|
|
174
|
+
const aStart = a.start;
|
|
175
|
+
const bStart = b.start;
|
|
176
|
+
return aStart.hour * 60 + aStart.minute - (bStart.hour * 60 + bStart.minute);
|
|
177
|
+
}
|
|
178
|
+
return 0;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return map;
|
|
182
|
+
});
|
|
183
|
+
function getEventsForDate(date) {
|
|
184
|
+
return eventsByDate.value.get(date.toString()) ?? [];
|
|
185
|
+
}
|
|
186
|
+
const weekdayLabels = computed(() => {
|
|
187
|
+
const labels = [];
|
|
188
|
+
const refSunday = asCalendarDate("2025-01-05");
|
|
189
|
+
for (let i = 0; i < 7; i++) {
|
|
190
|
+
const day = asCalendarDate(add(refSunday, i + options.weekStartsOn(), "day"));
|
|
191
|
+
labels.push(format(day, "ddd", options.locale()));
|
|
192
|
+
}
|
|
193
|
+
return labels;
|
|
194
|
+
});
|
|
195
|
+
const monthWeeks = computed(() => {
|
|
196
|
+
const grid = createMonth({
|
|
197
|
+
dateObj: displayDate.value,
|
|
198
|
+
weekStartsOn: options.weekStartsOn(),
|
|
199
|
+
fixedWeeks: monthConfig.value.fixedWeeks,
|
|
200
|
+
locale: options.locale()
|
|
201
|
+
});
|
|
202
|
+
const monthStart = startOf(displayDate.value, "month");
|
|
203
|
+
return grid.rows.map((week) => ({
|
|
204
|
+
days: week.map((dateVal) => {
|
|
205
|
+
const date = toCalendarDate(dateVal);
|
|
206
|
+
return {
|
|
207
|
+
date,
|
|
208
|
+
isCurrentMonth: isSameMonth(date, monthStart),
|
|
209
|
+
isToday: checkIsToday(date, getLocalTimeZone()),
|
|
210
|
+
isWeekend: checkIsWeekend(date, options.locale()),
|
|
211
|
+
events: getEventsForDate(date)
|
|
212
|
+
};
|
|
213
|
+
})
|
|
214
|
+
}));
|
|
215
|
+
});
|
|
216
|
+
const monthWeekLayouts = computed(() => {
|
|
217
|
+
return monthWeeks.value.map(
|
|
218
|
+
(week) => layoutMonthWeekEvents(week, monthConfig.value.maxEvents)
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
const weekDays = computed(() => {
|
|
222
|
+
const weekStart = startOfWeekByDay(asCalendarDate(displayDate.value), options.weekStartsOn());
|
|
223
|
+
const days = [];
|
|
224
|
+
for (let i = 0; i < 7; i++) {
|
|
225
|
+
const date = asCalendarDate(add(weekStart, i, "day"));
|
|
226
|
+
days.push({
|
|
227
|
+
date,
|
|
228
|
+
isCurrentMonth: true,
|
|
229
|
+
isToday: checkIsToday(date, getLocalTimeZone()),
|
|
230
|
+
isWeekend: checkIsWeekend(date, options.locale()),
|
|
231
|
+
events: getEventsForDate(date)
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return days;
|
|
235
|
+
});
|
|
236
|
+
const dayViewDate = computed(() => {
|
|
237
|
+
const date = asCalendarDate(displayDate.value);
|
|
238
|
+
return {
|
|
239
|
+
date,
|
|
240
|
+
isCurrentMonth: true,
|
|
241
|
+
isToday: checkIsToday(date, getLocalTimeZone()),
|
|
242
|
+
isWeekend: checkIsWeekend(date, options.locale()),
|
|
243
|
+
events: getEventsForDate(date)
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
const listDays = computed(() => {
|
|
247
|
+
const weekStart = startOfWeekByDay(asCalendarDate(displayDate.value), options.weekStartsOn());
|
|
248
|
+
const days = [];
|
|
249
|
+
for (let i = 0; i < 7; i++) {
|
|
250
|
+
const date = asCalendarDate(add(weekStart, i, "day"));
|
|
251
|
+
days.push({
|
|
252
|
+
date,
|
|
253
|
+
isCurrentMonth: true,
|
|
254
|
+
isToday: checkIsToday(date, getLocalTimeZone()),
|
|
255
|
+
isWeekend: checkIsWeekend(date, options.locale()),
|
|
256
|
+
events: getEventsForDate(date)
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return days;
|
|
260
|
+
});
|
|
261
|
+
function generateTimeSlots(startHour, endHour, slotDuration) {
|
|
262
|
+
const slots = [];
|
|
263
|
+
const totalMinutes = (endHour - startHour) * 60;
|
|
264
|
+
for (let m = 0; m < totalMinutes; m += slotDuration) {
|
|
265
|
+
const hour = startHour + Math.floor(m / 60);
|
|
266
|
+
const minute = m % 60;
|
|
267
|
+
const h12 = hour % 12 || 12;
|
|
268
|
+
const ampm = hour >= 12 ? "pm" : "am";
|
|
269
|
+
const label = minute === 0 ? `${h12}${ampm}` : "";
|
|
270
|
+
slots.push({ hour, minute, label });
|
|
271
|
+
}
|
|
272
|
+
return slots;
|
|
273
|
+
}
|
|
274
|
+
const weekTimeSlots = computed(
|
|
275
|
+
() => generateTimeSlots(weekConfig.value.startHour, weekConfig.value.endHour, weekConfig.value.slotDuration)
|
|
276
|
+
);
|
|
277
|
+
const dayTimeSlots = computed(
|
|
278
|
+
() => generateTimeSlots(dayConfig.value.startHour, dayConfig.value.endHour, dayConfig.value.slotDuration)
|
|
279
|
+
);
|
|
280
|
+
const timeGridDays = computed(() => {
|
|
281
|
+
const v = options.view();
|
|
282
|
+
if (v !== "week" && v !== "day") return [];
|
|
283
|
+
const days = v === "week" ? weekDays.value : [dayViewDate.value];
|
|
284
|
+
const config = v === "week" ? weekConfig.value : dayConfig.value;
|
|
285
|
+
return days.map((day) => ({
|
|
286
|
+
...day,
|
|
287
|
+
dateKey: day.date.toString(),
|
|
288
|
+
allDayEvents: day.events.filter((e) => e.allDay || !hasTimeComponent(e.start)),
|
|
289
|
+
timedEvents: layoutTimedEvents(getEventsForDate(day.date), config, SLOT_HEIGHT)
|
|
290
|
+
}));
|
|
291
|
+
});
|
|
292
|
+
const allDayLayout = computed(() => {
|
|
293
|
+
return layoutAllDayEvents(timeGridDays.value);
|
|
294
|
+
});
|
|
295
|
+
const timeGridSlots = computed(() => {
|
|
296
|
+
const v = options.view();
|
|
297
|
+
if (v === "week") return weekTimeSlots.value;
|
|
298
|
+
if (v === "day") return dayTimeSlots.value;
|
|
299
|
+
return [];
|
|
300
|
+
});
|
|
301
|
+
function formatTime(d) {
|
|
302
|
+
if (!hasTimeComponent(d)) return "";
|
|
303
|
+
const dt = d;
|
|
304
|
+
const h12 = dt.hour % 12 || 12;
|
|
305
|
+
const ampm = dt.hour >= 12 ? "pm" : "am";
|
|
306
|
+
const min = dt.minute > 0 ? `:${String(dt.minute).padStart(2, "0")}` : "";
|
|
307
|
+
return `${h12}${min}${ampm}`;
|
|
308
|
+
}
|
|
309
|
+
function formatTimeRange(start, end) {
|
|
310
|
+
const s = formatTime(start);
|
|
311
|
+
const e = formatTime(end);
|
|
312
|
+
if (!s) return "";
|
|
313
|
+
if (!e || s === e) return s;
|
|
314
|
+
return `${s}-${e}`;
|
|
315
|
+
}
|
|
316
|
+
function getEventStyle(event, bgOpacity = 10) {
|
|
317
|
+
const c = event.color;
|
|
318
|
+
const cssVar = c === "neutral" ? "var(--ui-bg-inverted)" : `var(--ui-${c})`;
|
|
319
|
+
return {
|
|
320
|
+
"--event-color": cssVar,
|
|
321
|
+
backgroundColor: `color-mix(in oklch, ${cssVar} ${bgOpacity}%, transparent)`
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function isEventStart(event, date) {
|
|
325
|
+
const startDate = toCalendarDate(event.start);
|
|
326
|
+
return date.compare(startDate) === 0;
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
// State
|
|
330
|
+
displayDate,
|
|
331
|
+
// Config
|
|
332
|
+
monthConfig,
|
|
333
|
+
weekConfig,
|
|
334
|
+
dayConfig,
|
|
335
|
+
// Grid data
|
|
336
|
+
monthWeeks,
|
|
337
|
+
monthWeekLayouts,
|
|
338
|
+
weekdayLabels,
|
|
339
|
+
weekDays,
|
|
340
|
+
dayViewDate,
|
|
341
|
+
listDays,
|
|
342
|
+
timeGridDays,
|
|
343
|
+
allDayLayout,
|
|
344
|
+
timeGridSlots,
|
|
345
|
+
// Header
|
|
346
|
+
headerTitle,
|
|
347
|
+
// Helpers
|
|
348
|
+
formatTime,
|
|
349
|
+
formatTimeRange,
|
|
350
|
+
getEventStyle,
|
|
351
|
+
isEventStart,
|
|
352
|
+
// Navigation
|
|
353
|
+
goToPrev,
|
|
354
|
+
goToNext,
|
|
355
|
+
goToToday,
|
|
356
|
+
setView,
|
|
357
|
+
// For drag-drop composable
|
|
358
|
+
normalizedEvents,
|
|
359
|
+
// Constant
|
|
360
|
+
SLOT_HEIGHT
|
|
361
|
+
};
|
|
362
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { InjectionKey } from "vue";
|
|
2
|
+
import type { EventCalendarContext } from "../types/event-calendar.js";
|
|
3
|
+
export declare const eventCalendarContextKey: InjectionKey<EventCalendarContext>;
|
|
4
|
+
/**
|
|
5
|
+
* Inject the EventCalendar context in a sub-component.
|
|
6
|
+
* Throws if called outside an <UEEventCalendar> root.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useEventCalendarContext(): EventCalendarContext;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { inject } from "vue";
|
|
2
|
+
export const eventCalendarContextKey = Symbol("nuxt-ui-elements-pro.event-calendar");
|
|
3
|
+
export function useEventCalendarContext() {
|
|
4
|
+
const ctx = inject(eventCalendarContextKey);
|
|
5
|
+
if (!ctx) {
|
|
6
|
+
throw new Error(
|
|
7
|
+
"[EventCalendar] useEventCalendarContext() was called outside of an <UEEventCalendar> root. Sub-components (UEEventCalendarHeader, UEEventCalendarMonthView, UEEventCalendarTimeGrid, UEEventCalendarListView) must be used as children of <UEEventCalendar>."
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
return ctx;
|
|
11
|
+
}
|
|
@@ -14,7 +14,7 @@ export declare function useEventCalendarDragDrop(options: UseEventCalendarDragDr
|
|
|
14
14
|
dropTargetKey: import("vue").Ref<string | null, string | null>;
|
|
15
15
|
dragSnapSlot: import("vue").Ref<string | null, string | null>;
|
|
16
16
|
onDragStart: (event: NormalizedEvent, e: DragEvent) => void;
|
|
17
|
-
onDragEnd: (
|
|
17
|
+
onDragEnd: () => void;
|
|
18
18
|
onDragOver: (key: string, e: DragEvent) => void;
|
|
19
19
|
onDragLeave: () => void;
|
|
20
20
|
onTimeGridDragOver: (dateKey: string, e: DragEvent) => void;
|
|
@@ -15,17 +15,11 @@ export function useEventCalendarDragDrop(options) {
|
|
|
15
15
|
draggedEventId.value = event.id;
|
|
16
16
|
e.dataTransfer.effectAllowed = "move";
|
|
17
17
|
e.dataTransfer.setData("text/plain", String(event.id));
|
|
18
|
-
if (e.target instanceof HTMLElement) {
|
|
19
|
-
e.target.style.opacity = "0.5";
|
|
20
|
-
}
|
|
21
18
|
}
|
|
22
|
-
function onDragEnd(
|
|
19
|
+
function onDragEnd() {
|
|
23
20
|
draggedEventId.value = null;
|
|
24
21
|
dropTargetKey.value = null;
|
|
25
22
|
dragSnapSlot.value = null;
|
|
26
|
-
if (e.target instanceof HTMLElement) {
|
|
27
|
-
e.target.style.opacity = "";
|
|
28
|
-
}
|
|
29
23
|
}
|
|
30
24
|
function onDragOver(key, e) {
|
|
31
25
|
if (!draggedEventId.value) return;
|
|
@@ -66,7 +60,11 @@ export function useEventCalendarDragDrop(options) {
|
|
|
66
60
|
oldStart: event.start,
|
|
67
61
|
oldEnd: event.end,
|
|
68
62
|
newStart: hasTimeComponent(event.start) ? asCalendarDateTime(newStart) : newStart,
|
|
69
|
-
newEnd: hasTimeComponent(event.end) ? asCalendarDateTime(newEnd) : newEnd
|
|
63
|
+
newEnd: hasTimeComponent(event.end) ? asCalendarDateTime(newEnd) : newEnd,
|
|
64
|
+
...event.recurringEventId != null && {
|
|
65
|
+
recurringEventId: event.recurringEventId,
|
|
66
|
+
occurrenceDate: hasTimeComponent(event.start) ? event.start : toCalendarDate(event.start)
|
|
67
|
+
}
|
|
70
68
|
});
|
|
71
69
|
draggedEventId.value = null;
|
|
72
70
|
}
|
|
@@ -96,7 +94,11 @@ export function useEventCalendarDragDrop(options) {
|
|
|
96
94
|
oldStart: event.start,
|
|
97
95
|
oldEnd: event.end,
|
|
98
96
|
newStart: newStartDate,
|
|
99
|
-
newEnd: newEndDate
|
|
97
|
+
newEnd: newEndDate,
|
|
98
|
+
...event.recurringEventId != null && {
|
|
99
|
+
recurringEventId: event.recurringEventId,
|
|
100
|
+
occurrenceDate: hasTimeComponent(event.start) ? event.start : toCalendarDate(event.start)
|
|
101
|
+
}
|
|
100
102
|
});
|
|
101
103
|
draggedEventId.value = null;
|
|
102
104
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CalendarDate } from "@internationalized/date";
|
|
2
|
+
import type { ComputedRef } from "vue";
|
|
3
|
+
import type { CalendarView, NormalizedEvent } from "../types/event-calendar.js";
|
|
4
|
+
export interface UseEventCalendarKeyboardOptions {
|
|
5
|
+
displayDate: ComputedRef<CalendarDate>;
|
|
6
|
+
view: () => CalendarView;
|
|
7
|
+
locale: () => string;
|
|
8
|
+
weekStartsOn: () => 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
9
|
+
goToPrev: () => void;
|
|
10
|
+
goToNext: () => void;
|
|
11
|
+
onUpdateModelValue: (date: CalendarDate) => void;
|
|
12
|
+
onDateSelect: (date: CalendarDate) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function useEventCalendarKeyboard(options: UseEventCalendarKeyboardOptions): {
|
|
15
|
+
focusedDate: import("vue").ShallowRef<CalendarDate, CalendarDate>;
|
|
16
|
+
isFocusedDate: (date: CalendarDate) => boolean;
|
|
17
|
+
onGridKeydown: (e: KeyboardEvent) => void;
|
|
18
|
+
formatDateAriaLabel: (date: CalendarDate) => string;
|
|
19
|
+
formatEventAriaLabel: (event: NormalizedEvent) => string;
|
|
20
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { isSameDay } from "@internationalized/date";
|
|
2
|
+
import { shallowRef, watch, nextTick } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
add,
|
|
5
|
+
subtract,
|
|
6
|
+
format,
|
|
7
|
+
asCalendarDate
|
|
8
|
+
} from "#std/date";
|
|
9
|
+
import { hasTimeComponent, startOfWeekByDay, endOfWeekByDay } from "../utils/event-calendar.js";
|
|
10
|
+
export function useEventCalendarKeyboard(options) {
|
|
11
|
+
const focusedDate = shallowRef(options.displayDate.value);
|
|
12
|
+
watch(options.displayDate, (newDate) => {
|
|
13
|
+
focusedDate.value = newDate;
|
|
14
|
+
});
|
|
15
|
+
function isFocusedDate(date) {
|
|
16
|
+
return isSameDay(focusedDate.value, date);
|
|
17
|
+
}
|
|
18
|
+
function focusCell(date) {
|
|
19
|
+
const el = document.querySelector(
|
|
20
|
+
`[data-date="${date.toString()}"][tabindex="0"]`
|
|
21
|
+
);
|
|
22
|
+
el?.focus();
|
|
23
|
+
}
|
|
24
|
+
function formatDateAriaLabel(date) {
|
|
25
|
+
return format(date, "dddd, MMMM D, YYYY", options.locale());
|
|
26
|
+
}
|
|
27
|
+
function formatEventAriaLabel(event) {
|
|
28
|
+
if (event.allDay || !hasTimeComponent(event.start)) {
|
|
29
|
+
return `${event.title}, all day`;
|
|
30
|
+
}
|
|
31
|
+
const startTime = formatTimeForAria(event.start);
|
|
32
|
+
const endTime = hasTimeComponent(event.end) ? formatTimeForAria(event.end) : "";
|
|
33
|
+
return endTime ? `${event.title}, ${startTime} to ${endTime}` : `${event.title}, ${startTime}`;
|
|
34
|
+
}
|
|
35
|
+
function formatTimeForAria(d) {
|
|
36
|
+
const h12 = d.hour % 12 || 12;
|
|
37
|
+
const ampm = d.hour >= 12 ? "PM" : "AM";
|
|
38
|
+
const min = d.minute > 0 ? `:${String(d.minute).padStart(2, "0")}` : "";
|
|
39
|
+
return `${h12}${min} ${ampm}`;
|
|
40
|
+
}
|
|
41
|
+
function onGridKeydown(e) {
|
|
42
|
+
const v = options.view();
|
|
43
|
+
if (v === "month") {
|
|
44
|
+
handleMonthKeydown(e);
|
|
45
|
+
} else if (v === "week" || v === "day") {
|
|
46
|
+
handleTimeGridKeydown(e, v);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function handleMonthKeydown(e) {
|
|
50
|
+
let newDate = null;
|
|
51
|
+
switch (e.key) {
|
|
52
|
+
case "ArrowRight":
|
|
53
|
+
newDate = asCalendarDate(add(focusedDate.value, 1, "day"));
|
|
54
|
+
break;
|
|
55
|
+
case "ArrowLeft":
|
|
56
|
+
newDate = asCalendarDate(subtract(focusedDate.value, 1, "day"));
|
|
57
|
+
break;
|
|
58
|
+
case "ArrowDown":
|
|
59
|
+
newDate = asCalendarDate(add(focusedDate.value, 7, "day"));
|
|
60
|
+
break;
|
|
61
|
+
case "ArrowUp":
|
|
62
|
+
newDate = asCalendarDate(subtract(focusedDate.value, 7, "day"));
|
|
63
|
+
break;
|
|
64
|
+
case "Home":
|
|
65
|
+
newDate = startOfWeekByDay(asCalendarDate(focusedDate.value), options.weekStartsOn());
|
|
66
|
+
break;
|
|
67
|
+
case "End":
|
|
68
|
+
newDate = endOfWeekByDay(asCalendarDate(focusedDate.value), options.weekStartsOn());
|
|
69
|
+
break;
|
|
70
|
+
case "PageDown":
|
|
71
|
+
newDate = asCalendarDate(add(focusedDate.value, 1, "month"));
|
|
72
|
+
break;
|
|
73
|
+
case "PageUp":
|
|
74
|
+
newDate = asCalendarDate(subtract(focusedDate.value, 1, "month"));
|
|
75
|
+
break;
|
|
76
|
+
case "Enter":
|
|
77
|
+
case " ":
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
options.onDateSelect(focusedDate.value);
|
|
80
|
+
return;
|
|
81
|
+
default:
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (newDate) {
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
focusedDate.value = newDate;
|
|
87
|
+
options.onUpdateModelValue(newDate);
|
|
88
|
+
nextTick(() => {
|
|
89
|
+
focusCell(newDate);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function handleTimeGridKeydown(e, v) {
|
|
94
|
+
switch (e.key) {
|
|
95
|
+
case "PageDown":
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
options.goToNext();
|
|
98
|
+
break;
|
|
99
|
+
case "PageUp":
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
options.goToPrev();
|
|
102
|
+
break;
|
|
103
|
+
case "ArrowLeft":
|
|
104
|
+
if (v === "week") {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
focusedDate.value = asCalendarDate(subtract(focusedDate.value, 1, "day"));
|
|
107
|
+
options.onUpdateModelValue(focusedDate.value);
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
case "ArrowRight":
|
|
111
|
+
if (v === "week") {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
focusedDate.value = asCalendarDate(add(focusedDate.value, 1, "day"));
|
|
114
|
+
options.onUpdateModelValue(focusedDate.value);
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
focusedDate,
|
|
123
|
+
isFocusedDate,
|
|
124
|
+
onGridKeydown,
|
|
125
|
+
formatDateAriaLabel,
|
|
126
|
+
formatEventAriaLabel
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ComputedRef } from "vue";
|
|
2
|
+
import type { CalendarEvent, EventResizePayload, NormalizedEvent, PositionedEvent } from "../types/event-calendar.js";
|
|
3
|
+
export interface UseEventCalendarResizeOptions {
|
|
4
|
+
/** Whether editing is enabled (getter, since it's a destructured prop) */
|
|
5
|
+
editable: () => boolean;
|
|
6
|
+
/** Reactive list of normalized events to look up by ID */
|
|
7
|
+
normalizedEvents: ComputedRef<NormalizedEvent[]>;
|
|
8
|
+
/** Returns the current view's grid config */
|
|
9
|
+
gridConfig: () => {
|
|
10
|
+
startHour: number;
|
|
11
|
+
endHour: number;
|
|
12
|
+
slotDuration: number;
|
|
13
|
+
};
|
|
14
|
+
/** Pixel height per slot */
|
|
15
|
+
slotHeight: number;
|
|
16
|
+
/** Callback invoked when an event is resized */
|
|
17
|
+
onEventResize: (payload: EventResizePayload) => void;
|
|
18
|
+
/** Callback invoked when resize starts */
|
|
19
|
+
onResizeStart: (payload: {
|
|
20
|
+
event: CalendarEvent;
|
|
21
|
+
}) => void;
|
|
22
|
+
/** Callback invoked when resize ends */
|
|
23
|
+
onResizeEnd: (payload: {
|
|
24
|
+
event: CalendarEvent;
|
|
25
|
+
}) => void;
|
|
26
|
+
}
|
|
27
|
+
export declare function useEventCalendarResize(options: UseEventCalendarResizeOptions): {
|
|
28
|
+
resizingEventId: import("vue").Ref<string | number | null, string | number | null>;
|
|
29
|
+
resizePreviewHeightPx: import("vue").Ref<number | null, number | null>;
|
|
30
|
+
onResizePointerDown: (event: PositionedEvent, e: PointerEvent) => void;
|
|
31
|
+
};
|