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.
Files changed (67) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +153 -18
  3. package/dist/runtime/components/EventCalendar.d.vue.ts +109 -22
  4. package/dist/runtime/components/EventCalendar.vue +225 -602
  5. package/dist/runtime/components/EventCalendar.vue.d.ts +109 -22
  6. package/dist/runtime/components/OrgChart.d.vue.ts +191 -0
  7. package/dist/runtime/components/OrgChart.vue +290 -0
  8. package/dist/runtime/components/OrgChart.vue.d.ts +191 -0
  9. package/dist/runtime/components/event-calendar/EventCalendarHeader.d.vue.ts +45 -0
  10. package/dist/runtime/components/event-calendar/EventCalendarHeader.vue +55 -0
  11. package/dist/runtime/components/event-calendar/EventCalendarHeader.vue.d.ts +45 -0
  12. package/dist/runtime/components/event-calendar/EventCalendarListView.d.vue.ts +25 -0
  13. package/dist/runtime/components/event-calendar/EventCalendarListView.vue +95 -0
  14. package/dist/runtime/components/event-calendar/EventCalendarListView.vue.d.ts +25 -0
  15. package/dist/runtime/components/event-calendar/EventCalendarMonthView.d.vue.ts +34 -0
  16. package/dist/runtime/components/event-calendar/EventCalendarMonthView.vue +336 -0
  17. package/dist/runtime/components/event-calendar/EventCalendarMonthView.vue.d.ts +34 -0
  18. package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.d.vue.ts +31 -0
  19. package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.vue +306 -0
  20. package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.vue.d.ts +31 -0
  21. package/dist/runtime/components/org-chart/OrgChartConnectors.d.vue.ts +15 -0
  22. package/dist/runtime/components/org-chart/OrgChartConnectors.vue +29 -0
  23. package/dist/runtime/components/org-chart/OrgChartConnectors.vue.d.ts +15 -0
  24. package/dist/runtime/components/org-chart/OrgChartControls.d.vue.ts +19 -0
  25. package/dist/runtime/components/org-chart/OrgChartControls.vue +25 -0
  26. package/dist/runtime/components/org-chart/OrgChartControls.vue.d.ts +19 -0
  27. package/dist/runtime/components/org-chart/OrgChartNode.d.vue.ts +30 -0
  28. package/dist/runtime/components/org-chart/OrgChartNode.vue +129 -0
  29. package/dist/runtime/components/org-chart/OrgChartNode.vue.d.ts +30 -0
  30. package/dist/runtime/composables/useEventCalendar.d.ts +52 -0
  31. package/dist/runtime/composables/useEventCalendar.js +362 -0
  32. package/dist/runtime/composables/useEventCalendarContext.d.ts +8 -0
  33. package/dist/runtime/composables/useEventCalendarContext.js +11 -0
  34. package/dist/runtime/composables/useEventCalendarDragDrop.d.ts +1 -1
  35. package/dist/runtime/composables/useEventCalendarDragDrop.js +11 -9
  36. package/dist/runtime/composables/useEventCalendarKeyboard.d.ts +20 -0
  37. package/dist/runtime/composables/useEventCalendarKeyboard.js +128 -0
  38. package/dist/runtime/composables/useEventCalendarResize.d.ts +31 -0
  39. package/dist/runtime/composables/useEventCalendarResize.js +87 -0
  40. package/dist/runtime/composables/useEventCalendarSelect.d.ts +21 -0
  41. package/dist/runtime/composables/useEventCalendarSelect.js +119 -0
  42. package/dist/runtime/composables/useOrgChart.d.ts +42 -0
  43. package/dist/runtime/composables/useOrgChart.js +154 -0
  44. package/dist/runtime/composables/useOrgChartContext.d.ts +8 -0
  45. package/dist/runtime/composables/useOrgChartContext.js +11 -0
  46. package/dist/runtime/composables/useOrgChartDragDrop.d.ts +20 -0
  47. package/dist/runtime/composables/useOrgChartDragDrop.js +67 -0
  48. package/dist/runtime/composables/useOrgChartKeyboard.d.ts +16 -0
  49. package/dist/runtime/composables/useOrgChartKeyboard.js +101 -0
  50. package/dist/runtime/composables/useOrgChartSearch.d.ts +16 -0
  51. package/dist/runtime/composables/useOrgChartSearch.js +62 -0
  52. package/dist/runtime/composables/useOrgChartZoom.d.ts +26 -0
  53. package/dist/runtime/composables/useOrgChartZoom.js +101 -0
  54. package/dist/runtime/index.d.ts +3 -0
  55. package/dist/runtime/index.js +2 -0
  56. package/dist/runtime/types/event-calendar.d.ts +170 -0
  57. package/dist/runtime/types/index.d.ts +2 -0
  58. package/dist/runtime/types/index.js +2 -0
  59. package/dist/runtime/types/org-chart.d.ts +181 -0
  60. package/dist/runtime/types/org-chart.js +0 -0
  61. package/dist/runtime/utils/event-calendar.d.ts +22 -1
  62. package/dist/runtime/utils/event-calendar.js +199 -1
  63. package/dist/runtime/utils/org-chart.d.ts +55 -0
  64. package/dist/runtime/utils/org-chart.js +367 -0
  65. package/dist/runtime/utils/recurrence.d.ts +30 -0
  66. package/dist/runtime/utils/recurrence.js +150 -0
  67. package/package.json +15 -6
@@ -1,650 +1,273 @@
1
1
  <script>
2
- import theme from "#build/ui-elements-pro/event-calendar";
3
- import { toCalendarDate } from "@internationalized/date";
4
- import { computed, ref, onMounted, onBeforeUnmount } from "vue";
2
+ import { Primitive } from "reka-ui";
3
+ import { computed, provide, ref, useSlots, watch } from "vue";
4
+ import { asCalendarDate } from "#std/date";
5
5
  import { tv } from "../utils/tv";
6
- import { hasTimeComponent, layoutTimedEvents } from "../utils/event-calendar";
6
+ import { useEventCalendar } from "../composables/useEventCalendar";
7
7
  import { useEventCalendarDragDrop } from "../composables/useEventCalendarDragDrop";
8
- import {
9
- today,
10
- startOf,
11
- add,
12
- subtract,
13
- format,
14
- isSameMonth,
15
- isToday as checkIsToday,
16
- isWeekend as checkIsWeekend,
17
- asCalendarDate,
18
- asCalendarDateTime,
19
- getLocalTimeZone,
20
- getWeekStartLocale
21
- } from "#std/date";
8
+ import { useEventCalendarKeyboard } from "../composables/useEventCalendarKeyboard";
9
+ import { useEventCalendarResize } from "../composables/useEventCalendarResize";
10
+ import { useEventCalendarSelect } from "../composables/useEventCalendarSelect";
11
+ import { eventCalendarContextKey } from "../composables/useEventCalendarContext";
12
+ import EventCalendarHeader from "./event-calendar/EventCalendarHeader.vue";
13
+ import EventCalendarMonthView from "./event-calendar/EventCalendarMonthView.vue";
14
+ import EventCalendarTimeGrid from "./event-calendar/EventCalendarTimeGrid.vue";
15
+ import EventCalendarListView from "./event-calendar/EventCalendarListView.vue";
16
+ import theme from "#build/ui-elements-pro/event-calendar";
22
17
  </script>
23
18
 
24
19
  <script setup>
25
- const {
26
- events = [],
27
- view = "month",
28
- locale = "en-US",
29
- weekStartsOn = 0,
30
- editable = true,
31
- color = "primary",
32
- monthOptions: monthOpts,
33
- weekOptions: weekOpts,
34
- dayOptions: dayOpts,
35
- ui: propUi,
36
- ...props
37
- } = defineProps({
20
+ const props = defineProps({
21
+ as: { type: null, required: false },
38
22
  events: { type: Array, required: false },
39
23
  modelValue: { type: null, required: false },
40
24
  view: { type: String, required: false },
25
+ views: { type: Array, required: false },
41
26
  locale: { type: String, required: false },
42
27
  weekStartsOn: { type: Number, required: false },
43
- editable: { type: Boolean, required: false },
28
+ editable: { type: Boolean, required: false, default: true },
29
+ loading: { type: Boolean, required: false },
30
+ selectable: { type: Boolean, required: false, default: true },
44
31
  color: { type: null, required: false },
32
+ headerTitle: { type: [String, Function], required: false },
45
33
  monthOptions: { type: Object, required: false },
46
34
  weekOptions: { type: Object, required: false },
47
35
  dayOptions: { type: Object, required: false },
48
36
  ui: { type: Object, required: false }
49
37
  });
50
- const emit = defineEmits(["update:modelValue", "update:view", "dateClick", "eventClick", "eventDrop"]);
38
+ const emit = defineEmits(["update:modelValue", "update:view", "dateClick", "eventClick", "eventDrop", "eventResize", "eventResizeStart", "eventResizeEnd", "select"]);
51
39
  defineSlots();
52
- const monthConfig = computed(() => ({
53
- maxEvents: monthOpts?.maxEvents ?? 3,
54
- fixedWeeks: monthOpts?.fixedWeeks ?? true
55
- }));
56
- const weekConfig = computed(() => ({
57
- startHour: weekOpts?.startHour ?? 7,
58
- endHour: weekOpts?.endHour ?? 22,
59
- slotDuration: weekOpts?.slotDuration ?? 30
60
- }));
61
- const dayConfig = computed(() => ({
62
- startHour: dayOpts?.startHour ?? 7,
63
- endHour: dayOpts?.endHour ?? 22,
64
- slotDuration: dayOpts?.slotDuration ?? 30
65
- }));
40
+ const slots = useSlots();
41
+ const ALL_VIEWS = ["month", "week", "day", "list"];
42
+ const internalView = ref(props.view ?? "month");
43
+ watch(() => props.view, (v) => {
44
+ if (v !== void 0) internalView.value = v;
45
+ });
46
+ function updateView(v) {
47
+ internalView.value = v;
48
+ emit("update:view", v);
49
+ }
66
50
  const ui = computed(
67
51
  () => tv({
68
52
  extend: tv(theme)
69
53
  })({
70
- color
54
+ color: props.color ?? "primary"
71
55
  })
72
56
  );
73
- const displayDate = computed(() => {
74
- if (props.modelValue) {
75
- return asCalendarDate(props.modelValue);
76
- }
77
- return today();
78
- });
79
- const weekLocale = computed(() => getWeekStartLocale(weekStartsOn));
80
- function goToPrev() {
81
- let target;
82
- if (view === "month") {
83
- target = startOf(subtract(displayDate.value, 1, "month"), "month");
84
- } else if (view === "week") {
85
- target = asCalendarDate(subtract(displayDate.value, 1, "week"));
86
- } else {
87
- target = asCalendarDate(subtract(displayDate.value, 1, "day"));
88
- }
89
- emit("update:modelValue", target);
90
- }
91
- function goToNext() {
92
- let target;
93
- if (view === "month") {
94
- target = startOf(add(displayDate.value, 1, "month"), "month");
95
- } else if (view === "week") {
96
- target = asCalendarDate(add(displayDate.value, 1, "week"));
97
- } else {
98
- target = asCalendarDate(add(displayDate.value, 1, "day"));
99
- }
100
- emit("update:modelValue", target);
101
- }
102
- function goToToday() {
103
- emit("update:modelValue", today());
104
- }
105
- function setView(v) {
106
- emit("update:view", v);
107
- }
108
- const headerTitle = computed(() => {
109
- if (view === "month") {
110
- return format(displayDate.value, "MMMM YYYY", locale);
111
- }
112
- if (view === "week") {
113
- const weekStart = startOf(displayDate.value, "week", weekLocale.value);
114
- const weekEnd = asCalendarDate(add(weekStart, 6, "day"));
115
- if (weekStart.month === weekEnd.month) {
116
- return `${format(weekStart, "MMM D", locale)} \u2013 ${format(weekEnd, "D, YYYY", locale)}`;
117
- }
118
- return `${format(weekStart, "MMM D", locale)} \u2013 ${format(weekEnd, "MMM D, YYYY", locale)}`;
119
- }
120
- return format(displayDate.value, "dddd, MMMM D, YYYY", locale);
57
+ const calendar = useEventCalendar({
58
+ events: () => props.events ?? [],
59
+ modelValue: () => props.modelValue,
60
+ view: () => internalView.value,
61
+ locale: () => props.locale ?? "en-US",
62
+ weekStartsOn: () => props.weekStartsOn ?? 0,
63
+ monthOptions: () => props.monthOptions,
64
+ weekOptions: () => props.weekOptions,
65
+ dayOptions: () => props.dayOptions,
66
+ onUpdateModelValue: (date) => emit("update:modelValue", date),
67
+ onUpdateView: updateView
121
68
  });
122
- function parseDateInput(input) {
123
- if (typeof input === "object" && "calendar" in input) {
124
- return input;
125
- }
126
- if (input instanceof Date) {
127
- const iso = input.toISOString();
128
- if (iso.endsWith("T00:00:00.000Z")) {
129
- return asCalendarDate(input);
130
- }
131
- return asCalendarDateTime(input);
132
- }
133
- if (typeof input === "string") {
134
- if (input.includes("T") || input.includes(" ")) {
135
- try {
136
- return asCalendarDateTime(input);
137
- } catch {
138
- return asCalendarDate(input);
139
- }
140
- }
141
- return asCalendarDate(input);
142
- }
143
- return today();
144
- }
145
- const normalizedEvents = computed(() => {
146
- return events.map((event) => {
147
- const start = parseDateInput(event.start);
148
- const end = event.end ? parseDateInput(event.end) : start;
149
- const allDay = event.allDay ?? !hasTimeComponent(start);
150
- return {
151
- id: event.id,
152
- title: event.title,
153
- start,
154
- end,
155
- color: event.color ?? "primary",
156
- allDay,
157
- draggable: event.draggable ?? true,
158
- original: event
159
- };
160
- });
161
- });
162
- const eventsByDate = computed(() => {
163
- const map = /* @__PURE__ */ new Map();
164
- for (const event of normalizedEvents.value) {
165
- const startDate = toCalendarDate(event.start);
166
- const endDate = toCalendarDate(event.end);
167
- let current = startDate;
168
- while (current.compare(endDate) <= 0) {
169
- const key = current.toString();
170
- const existing = map.get(key) ?? [];
171
- existing.push(event);
172
- map.set(key, existing);
173
- current = asCalendarDate(add(current, 1, "day"));
174
- }
175
- }
176
- return map;
177
- });
178
- function getEventsForDate(date) {
179
- return eventsByDate.value.get(date.toString()) ?? [];
180
- }
181
- const weekdayLabels = computed(() => {
182
- const labels = [];
183
- const refSunday = asCalendarDate("2025-01-05");
184
- for (let i = 0; i < 7; i++) {
185
- const day = asCalendarDate(add(refSunday, i + weekStartsOn, "day"));
186
- labels.push(format(day, "ddd", locale));
187
- }
188
- return labels;
189
- });
190
- const monthWeeks = computed(() => {
191
- const monthStart = startOf(displayDate.value, "month");
192
- const gridStart = startOf(monthStart, "week", weekLocale.value);
193
- const rowCount = monthConfig.value.fixedWeeks ? 6 : 5;
194
- const result = [];
195
- let current = gridStart;
196
- for (let week = 0; week < rowCount; week++) {
197
- const days = [];
198
- for (let day = 0; day < 7; day++) {
199
- const date = asCalendarDate(current);
200
- days.push({
201
- date,
202
- isCurrentMonth: isSameMonth(date, monthStart),
203
- isToday: checkIsToday(date, getLocalTimeZone()),
204
- isWeekend: checkIsWeekend(date, weekLocale.value),
205
- events: getEventsForDate(date)
206
- });
207
- current = asCalendarDate(add(current, 1, "day"));
208
- }
209
- result.push({ days });
210
- }
211
- return result;
212
- });
213
- const weekDays = computed(() => {
214
- const weekStart = startOf(displayDate.value, "week", weekLocale.value);
215
- const days = [];
216
- for (let i = 0; i < 7; i++) {
217
- const date = asCalendarDate(add(weekStart, i, "day"));
218
- days.push({
219
- date,
220
- isCurrentMonth: true,
221
- isToday: checkIsToday(date, getLocalTimeZone()),
222
- isWeekend: checkIsWeekend(date, weekLocale.value),
223
- events: getEventsForDate(date)
224
- });
225
- }
226
- return days;
69
+ const dragDrop = useEventCalendarDragDrop({
70
+ editable: () => props.editable ?? true,
71
+ normalizedEvents: calendar.normalizedEvents,
72
+ onEventDrop: (payload) => emit("eventDrop", payload)
227
73
  });
228
- const dayViewDate = computed(() => {
229
- const date = asCalendarDate(displayDate.value);
230
- return {
231
- date,
232
- isCurrentMonth: true,
233
- isToday: checkIsToday(date, getLocalTimeZone()),
234
- isWeekend: checkIsWeekend(date, weekLocale.value),
235
- events: getEventsForDate(date)
236
- };
74
+ const resize = useEventCalendarResize({
75
+ editable: () => props.editable ?? true,
76
+ normalizedEvents: calendar.normalizedEvents,
77
+ gridConfig: () => {
78
+ const v = internalView.value;
79
+ return v === "day" ? calendar.dayConfig.value : calendar.weekConfig.value;
80
+ },
81
+ slotHeight: calendar.SLOT_HEIGHT,
82
+ onEventResize: (payload) => emit("eventResize", payload),
83
+ onResizeStart: (payload) => emit("eventResizeStart", payload),
84
+ onResizeEnd: (payload) => emit("eventResizeEnd", payload)
237
85
  });
238
- function generateTimeSlots(startHour, endHour, slotDuration) {
239
- const slots = [];
240
- const totalMinutes = (endHour - startHour) * 60;
241
- for (let m = 0; m < totalMinutes; m += slotDuration) {
242
- const hour = startHour + Math.floor(m / 60);
243
- const minute = m % 60;
244
- const h12 = hour % 12 || 12;
245
- const ampm = hour >= 12 ? "pm" : "am";
246
- const label = minute === 0 ? `${h12}${ampm}` : "";
247
- slots.push({ hour, minute, label });
248
- }
249
- return slots;
250
- }
251
- const weekTimeSlots = computed(
252
- () => generateTimeSlots(weekConfig.value.startHour, weekConfig.value.endHour, weekConfig.value.slotDuration)
253
- );
254
- const dayTimeSlots = computed(
255
- () => generateTimeSlots(dayConfig.value.startHour, dayConfig.value.endHour, dayConfig.value.slotDuration)
256
- );
257
- const SLOT_HEIGHT = 48;
258
- const timeGridDays = computed(() => {
259
- if (view !== "week" && view !== "day") return [];
260
- const days = view === "week" ? weekDays.value : [dayViewDate.value];
261
- const config = view === "week" ? weekConfig.value : dayConfig.value;
262
- return days.map((day) => ({
263
- ...day,
264
- dateKey: day.date.toString(),
265
- allDayEvents: day.events.filter((e) => e.allDay || !hasTimeComponent(e.start)),
266
- timedEvents: layoutTimedEvents(getEventsForDate(day.date), config, SLOT_HEIGHT)
267
- }));
86
+ const keyboard = useEventCalendarKeyboard({
87
+ displayDate: calendar.displayDate,
88
+ view: () => internalView.value,
89
+ locale: () => props.locale ?? "en-US",
90
+ weekStartsOn: () => props.weekStartsOn ?? 0,
91
+ goToPrev: calendar.goToPrev,
92
+ goToNext: calendar.goToNext,
93
+ onUpdateModelValue: (date) => emit("update:modelValue", date),
94
+ onDateSelect: (date) => emit("dateClick", date)
268
95
  });
269
- const timeGridSlots = computed(() => {
270
- if (view === "week") return weekTimeSlots.value;
271
- if (view === "day") return dayTimeSlots.value;
272
- return [];
96
+ const selection = useEventCalendarSelect({
97
+ selectable: () => props.selectable ?? true,
98
+ view: () => internalView.value,
99
+ slotDuration: () => {
100
+ const v = internalView.value;
101
+ return v === "day" ? props.dayOptions?.slotDuration ?? 30 : props.weekOptions?.slotDuration ?? 30;
102
+ },
103
+ startHour: () => {
104
+ const v = internalView.value;
105
+ return v === "day" ? props.dayOptions?.startHour ?? 7 : props.weekOptions?.startHour ?? 7;
106
+ },
107
+ endHour: () => {
108
+ const v = internalView.value;
109
+ return v === "day" ? props.dayOptions?.endHour ?? 22 : props.weekOptions?.endHour ?? 22;
110
+ },
111
+ onSelect: (payload) => emit("select", payload)
273
112
  });
274
- function formatTime(d) {
275
- if (!hasTimeComponent(d)) return "";
276
- const dt = d;
277
- const h12 = dt.hour % 12 || 12;
278
- const ampm = dt.hour >= 12 ? "pm" : "am";
279
- const min = dt.minute > 0 ? `:${String(dt.minute).padStart(2, "0")}` : "";
280
- return `${h12}${min}${ampm}`;
281
- }
282
- function getEventStyle(event, bgOpacity = 10) {
283
- const c = event.color;
284
- const cssVar = c === "neutral" ? "var(--ui-bg-inverted)" : `var(--ui-${c})`;
285
- return {
286
- "--event-color": cssVar,
287
- backgroundColor: `color-mix(in oklch, ${cssVar} ${bgOpacity}%, transparent)`
288
- };
289
- }
290
- function isEventStart(event, date) {
291
- const startDate = toCalendarDate(event.start);
292
- return date.compare(startDate) === 0;
293
- }
294
113
  function handleDateClick(day) {
114
+ if (selection.preventNextClick.value) {
115
+ selection.preventNextClick.value = false;
116
+ return;
117
+ }
118
+ keyboard.focusedDate.value = day.date;
295
119
  emit("dateClick", day.date);
296
120
  }
297
121
  function handleEventClick(event, e) {
298
122
  e.stopPropagation();
299
123
  emit("eventClick", event.original);
300
124
  }
301
- const expandedDay = ref(null);
302
- function openExpandDay(dateKey, e) {
303
- e.stopPropagation();
304
- expandedDay.value = dateKey;
305
- }
306
- function closeExpandDay() {
307
- expandedDay.value = null;
308
- }
309
- function isExpanded(dateKey) {
310
- return expandedDay.value === dateKey;
311
- }
312
- onMounted(() => {
313
- document.addEventListener("click", closeExpandDay);
314
- });
315
- onBeforeUnmount(() => {
316
- document.removeEventListener("click", closeExpandDay);
125
+ const resolvedHeaderTitle = computed(() => {
126
+ if (typeof props.headerTitle === "string") return props.headerTitle;
127
+ if (typeof props.headerTitle === "function") {
128
+ return props.headerTitle(calendar.displayDate.value, internalView.value);
129
+ }
130
+ return calendar.headerTitle.value;
317
131
  });
318
- const {
319
- draggedEventId,
320
- dropTargetKey,
321
- dragSnapSlot,
322
- onDragStart,
323
- onDragEnd,
324
- onDragOver,
325
- onDragLeave,
326
- onTimeGridDragOver,
327
- onSlotDragOver,
328
- onDropMonthCell,
329
- onDropTimeGrid
330
- } = useEventCalendarDragDrop({
331
- editable: () => editable,
332
- normalizedEvents,
333
- onEventDrop: (payload) => emit("eventDrop", payload)
132
+ const resolvedViews = computed(() => props.views ?? ALL_VIEWS);
133
+ const ctx = {
134
+ displayDate: calendar.displayDate,
135
+ view: computed(() => internalView.value),
136
+ locale: computed(() => props.locale ?? "en-US"),
137
+ weekStartsOn: computed(() => props.weekStartsOn ?? 0),
138
+ editable: computed(() => props.editable ?? true),
139
+ loading: computed(() => props.loading ?? false),
140
+ color: computed(() => props.color ?? "primary"),
141
+ views: resolvedViews,
142
+ monthConfig: calendar.monthConfig,
143
+ weekConfig: calendar.weekConfig,
144
+ dayConfig: calendar.dayConfig,
145
+ monthWeeks: calendar.monthWeeks,
146
+ monthWeekLayouts: calendar.monthWeekLayouts,
147
+ weekdayLabels: calendar.weekdayLabels,
148
+ weekDays: calendar.weekDays,
149
+ dayViewDate: calendar.dayViewDate,
150
+ listDays: calendar.listDays,
151
+ timeGridDays: calendar.timeGridDays,
152
+ allDayLayout: calendar.allDayLayout,
153
+ timeGridSlots: calendar.timeGridSlots,
154
+ headerTitle: resolvedHeaderTitle,
155
+ formatTime: calendar.formatTime,
156
+ formatTimeRange: calendar.formatTimeRange,
157
+ getEventStyle: calendar.getEventStyle,
158
+ isEventStart: calendar.isEventStart,
159
+ goToPrev: calendar.goToPrev,
160
+ goToNext: calendar.goToNext,
161
+ goToToday: calendar.goToToday,
162
+ setView: calendar.setView,
163
+ handleDateClick,
164
+ handleEventClick,
165
+ ui,
166
+ propUi: computed(() => props.ui),
167
+ ...dragDrop,
168
+ ...resize,
169
+ ...keyboard,
170
+ // Selection
171
+ selectable: computed(() => props.selectable ?? true),
172
+ ...selection,
173
+ SLOT_HEIGHT: calendar.SLOT_HEIGHT
174
+ };
175
+ provide(eventCalendarContextKey, ctx);
176
+ function setDate(date) {
177
+ emit("update:modelValue", asCalendarDate(date));
178
+ }
179
+ defineExpose({
180
+ next: ctx.goToNext,
181
+ prev: ctx.goToPrev,
182
+ today: ctx.goToToday,
183
+ setView: ctx.setView,
184
+ setDate,
185
+ view: ctx.view,
186
+ date: ctx.displayDate
334
187
  });
335
188
  </script>
336
189
 
337
190
  <template>
338
- <div data-slot="root" :class="ui.root({ class: propUi?.root })">
339
- <!-- Header -->
340
- <slot
341
- name="header"
342
- :title="headerTitle"
343
- :prev="goToPrev"
344
- :next="goToNext"
345
- :today="goToToday"
346
- :current-date="displayDate"
347
- :view="view"
348
- :set-view="setView">
349
- <div data-slot="header" :class="ui.header({ class: propUi?.header })">
350
- <span data-slot="headerTitle" :class="ui.headerTitle({ class: propUi?.headerTitle })">
351
- {{ headerTitle }}
352
- </span>
353
-
354
- <div data-slot="headerActions" :class="ui.headerActions({ class: propUi?.headerActions })">
355
- <!-- View switcher -->
356
- <div data-slot="viewSwitcher" :class="ui.viewSwitcher({ class: propUi?.viewSwitcher })">
357
- <UButton
358
- v-for="v in ['month', 'week', 'day']"
359
- :key="v"
360
- :label="v"
361
- size="xs"
362
- :color="view === v ? color : 'neutral'"
363
- :variant="view === v ? 'subtle' : 'ghost'"
364
- class="capitalize"
365
- @click="setView(v)" />
366
- </div>
367
-
368
- <UButton icon="i-lucide-chevron-left" color="neutral" variant="ghost" size="xs" square @click="goToPrev" />
369
- <UButton label="Today" color="neutral" variant="ghost" size="xs" @click="goToToday" />
370
- <UButton icon="i-lucide-chevron-right" color="neutral" variant="ghost" size="xs" square @click="goToNext" />
371
- </div>
372
- </div>
373
- </slot>
374
-
375
- <!-- ── Month View ──────────────────────────────────────────────── -->
376
- <template v-if="view === 'month'">
377
- <!-- Weekday headers -->
378
- <div data-slot="weekdayRow" :class="ui.weekdayRow({ class: propUi?.weekdayRow })">
379
- <div
380
- v-for="(day, index) in weekdayLabels"
381
- :key="day"
382
- data-slot="weekdayCell"
383
- :class="ui.weekdayCell({ class: propUi?.weekdayCell })">
384
- <slot name="day-header" :day="day" :index="index">
385
- {{ day }}
386
- </slot>
387
- </div>
388
- </div>
191
+ <Primitive :as="props.as ?? 'div'" data-slot="root" :class="ui.root({ class: props.ui?.root })">
192
+ <!-- Compound mode: user provides #default and owns rendering -->
193
+ <slot v-if="slots.default" v-bind="ctx" />
389
194
 
390
- <!-- Month grid -->
391
- <div data-slot="monthBody" :class="ui.monthBody({ class: propUi?.monthBody })">
392
- <template v-for="week in monthWeeks" :key="week.days[0].date.toString()">
393
- <div
394
- v-for="day in week.days"
395
- :key="day.date.toString()"
396
- data-slot="dayCell"
397
- :class="[
398
- ui.dayCell({ class: propUi?.dayCell }),
399
- !day.isCurrentMonth && ui.dayCellOutside({ class: propUi?.dayCellOutside }),
400
- dropTargetKey === `month-${day.date.toString()}` && ui.dropTarget({ class: propUi?.dropTarget }),
401
- isExpanded(day.date.toString()) && 'relative z-10'
402
- ]"
403
- @click="handleDateClick(day)"
404
- @dragover="onDragOver(`month-${day.date.toString()}`, $event)"
405
- @dragleave="onDragLeave"
406
- @drop="onDropMonthCell(day.date, $event)">
407
- <slot name="day" :day="day">
408
- <!-- Day number -->
409
- <div
410
- data-slot="dayNumber"
411
- :class="[
412
- !day.isToday && ui.dayNumber({ class: propUi?.dayNumber }),
413
- day.isToday && ui.dayNumberToday({ class: propUi?.dayNumberToday }),
414
- !day.isCurrentMonth && !day.isToday && ui.dayNumberOutside({ class: propUi?.dayNumberOutside })
415
- ]">
416
- {{ day.date.day }}
417
- </div>
418
-
419
- <!-- Event chips -->
420
- <div data-slot="eventList" :class="ui.eventList({ class: propUi?.eventList })">
421
- <template v-for="(event, idx) in day.events" :key="event.id">
422
- <div
423
- v-if="idx < monthConfig.maxEvents"
424
- data-slot="eventChip"
425
- :draggable="editable && event.draggable && isEventStart(event, day.date)"
426
- :class="[ui.eventChip({ class: propUi?.eventChip }), !isEventStart(event, day.date) && 'border-l-0!']"
427
- :style="getEventStyle(event)"
428
- @click="handleEventClick(event, $event)"
429
- @dragstart="onDragStart(event, $event)"
430
- @dragend="onDragEnd">
431
- <slot name="event" :event="event.original" :view="view">
432
- <template v-if="isEventStart(event, day.date)">
433
- <span v-if="!event.allDay" data-slot="eventTime" :class="ui.eventTime({ class: propUi?.eventTime })">{{
434
- formatTime(event.start)
435
- }}</span>
436
- {{ event.title }}
437
- </template>
438
- <span v-else class="opacity-0">{{ event.title }}</span>
439
- </slot>
440
- </div>
441
- </template>
442
-
443
- <!-- "+N more" trigger -->
444
- <div
445
- v-if="day.events.length > monthConfig.maxEvents"
446
- data-slot="moreEvents"
447
- :class="ui.moreEvents({ class: propUi?.moreEvents })"
448
- @click="openExpandDay(day.date.toString(), $event)">
449
- <slot
450
- name="more-events"
451
- :events="day.events.slice(monthConfig.maxEvents).map((e) => e.original)"
452
- :count="day.events.length - monthConfig.maxEvents"
453
- :day="day">
454
- +{{ day.events.length - monthConfig.maxEvents }} more
455
- </slot>
456
- </div>
457
- </div>
458
-
459
- <!-- Expanded overlay with ALL events -->
460
- <div
461
- v-if="isExpanded(day.date.toString()) && day.events.length > monthConfig.maxEvents"
462
- data-slot="expandedOverlay"
463
- :class="ui.expandedOverlay({ class: propUi?.expandedOverlay })"
464
- @click.stop>
465
- <div
466
- data-slot="dayNumber"
467
- :class="[
468
- !day.isToday && ui.dayNumber({ class: propUi?.dayNumber }),
469
- day.isToday && ui.dayNumberToday({ class: propUi?.dayNumberToday })
470
- ]">
471
- {{ day.date.day }}
472
- </div>
473
- <div data-slot="eventList" :class="ui.eventList({ class: propUi?.eventList })">
474
- <div
475
- v-for="event in day.events"
476
- :key="`expanded-${event.id}`"
477
- data-slot="eventChip"
478
- :draggable="editable && event.draggable"
479
- :class="ui.eventChip({ class: propUi?.eventChip })"
480
- :style="getEventStyle(event)"
481
- @click="handleEventClick(event, $event)"
482
- @dragstart="onDragStart(event, $event)"
483
- @dragend="onDragEnd">
484
- <slot name="event" :event="event.original" :view="view">
485
- <span v-if="!event.allDay" data-slot="eventTime" :class="ui.eventTime({ class: propUi?.eventTime })">{{
486
- formatTime(event.start)
487
- }}</span
488
- >{{ event.title }}
489
- </slot>
490
- </div>
491
- </div>
492
- </div>
493
- </slot>
494
- </div>
195
+ <!-- Auto mode: render header + active view with slot forwarding -->
196
+ <template v-else>
197
+ <!-- Header -->
198
+ <slot
199
+ v-if="slots.header"
200
+ name="header"
201
+ :title="resolvedHeaderTitle"
202
+ :prev="ctx.goToPrev"
203
+ :next="ctx.goToNext"
204
+ :today="ctx.goToToday"
205
+ :current-date="ctx.displayDate.value"
206
+ :view="ctx.view.value"
207
+ :set-view="ctx.setView"
208
+ :views="resolvedViews" />
209
+ <EventCalendarHeader v-else>
210
+ <template v-if="slots['header-title']" #title="titleProps">
211
+ <slot name="header-title" v-bind="titleProps" />
495
212
  </template>
496
- </div>
497
- </template>
498
-
499
- <!-- ── Week / Day View (unified) ───────────────────────────────── -->
500
- <template v-else-if="view === 'week' || view === 'day'">
501
- <!-- Column headers -->
502
- <div data-slot="columnHeaders" :class="ui.columnHeaders({ class: propUi?.columnHeaders })">
503
- <div data-slot="timeGutterSpacer" :class="ui.timeGutterSpacer({ class: propUi?.timeGutterSpacer })" />
504
- <div
505
- v-for="day in timeGridDays"
506
- :key="day.dateKey"
507
- data-slot="dayColumnHeader"
508
- :class="ui.dayColumnHeader({ class: propUi?.dayColumnHeader })">
509
- <div :class="ui.dayColumnHeaderLabel({ class: propUi?.dayColumnHeaderLabel })">
510
- {{ format(day.date, timeGridDays.length === 1 ? "dddd" : "ddd", locale) }}
511
- </div>
512
- <div
513
- :class="[
514
- day.isToday ? ui.dayColumnHeaderToday({ class: propUi?.dayColumnHeaderToday }) : ui.dayColumnHeaderNumber({ class: propUi?.dayColumnHeaderNumber })
515
- ]">
516
- {{ day.date.day }}
517
- </div>
518
- </div>
519
- </div>
520
-
521
- <!-- All-day events row -->
522
- <div data-slot="allDayRow" :class="ui.allDayRow({ class: propUi?.allDayRow })">
523
- <div data-slot="allDayLabel" :class="ui.allDayLabel({ class: propUi?.allDayLabel })">all-day</div>
524
- <div
525
- v-for="day in timeGridDays"
526
- :key="`allday-${day.dateKey}`"
527
- data-slot="allDayCell"
528
- :class="[
529
- 'overflow-auto',
530
- ui.allDayCell({ class: propUi?.allDayCell }),
531
- dropTargetKey === `allday-${day.dateKey}` && ui.dropTarget({ class: propUi?.dropTarget })
532
- ]"
533
- @dragover="onDragOver(`allday-${day.dateKey}`, $event)"
534
- @dragleave="onDragLeave"
535
- @drop="onDropMonthCell(day.date, $event)">
536
- <!-- Day view exposes the all-day slot; week view renders chips directly -->
537
- <template v-if="timeGridDays.length === 1">
538
- <slot name="all-day" :events="day.allDayEvents.map((e) => e.original)" :date="day.date">
539
- <div
540
- v-for="event in day.allDayEvents"
541
- :key="event.id"
542
- :draggable="editable && event.draggable"
543
- :class="[ui.eventChip({ class: propUi?.eventChip }), 'w-full']"
544
- :style="getEventStyle(event)"
545
- @click="handleEventClick(event, $event)"
546
- @dragstart="onDragStart(event, $event)"
547
- @dragend="onDragEnd">
548
- <slot name="event" :event="event.original" :view="view">
549
- {{ event.title }}
550
- </slot>
551
- </div>
552
- </slot>
553
- </template>
554
- <template v-else>
555
- <div
556
- v-for="event in day.allDayEvents"
557
- :key="event.id"
558
- :draggable="editable && event.draggable && isEventStart(event, day.date)"
559
- :class="[ui.eventChip({ class: propUi?.eventChip }), 'w-full', !isEventStart(event, day.date) && 'border-l-0!']"
560
- :style="getEventStyle(event)"
561
- @click="handleEventClick(event, $event)"
562
- @dragstart="onDragStart(event, $event)"
563
- @dragend="onDragEnd">
564
- <slot name="event" :event="event.original" :view="view">
565
- <template v-if="isEventStart(event, day.date)">
566
- {{ event.title }}
567
- </template>
568
- <span v-else class="opacity-0">{{ event.title }}</span>
569
- </slot>
570
- </div>
571
- </template>
572
- </div>
573
- </div>
213
+ <template v-if="slots['header-nav']" #nav="navProps">
214
+ <slot name="header-nav" v-bind="navProps" />
215
+ </template>
216
+ <template v-if="slots['header-view-switcher']" #view-switcher="vsProps">
217
+ <slot name="header-view-switcher" v-bind="vsProps" />
218
+ </template>
219
+ </EventCalendarHeader>
574
220
 
575
- <!-- Time grid -->
576
- <div data-slot="timeGrid" :class="ui.timeGrid({ class: propUi?.timeGrid })">
577
- <!-- Time gutter -->
578
- <div data-slot="timeGutter" :class="ui.timeGutter({ class: propUi?.timeGutter })">
579
- <div
580
- v-for="slot in timeGridSlots"
581
- :key="`gutter-${slot.hour}-${slot.minute}`"
582
- data-slot="timeGutterSlot"
583
- :class="ui.timeGutterSlot({ class: propUi?.timeGutterSlot })"
584
- :style="{ height: `${SLOT_HEIGHT}px` }">
585
- <div v-if="slot.label" data-slot="timeLabel" :class="ui.timeLabel({ class: propUi?.timeLabel })">
586
- <slot name="time-label" :hour="slot.hour" :label="slot.label">
587
- {{ slot.label }}
588
- </slot>
589
- </div>
590
- </div>
591
- </div>
221
+ <!-- Month view -->
222
+ <EventCalendarMonthView v-if="ctx.view.value === 'month'">
223
+ <template v-if="slots.event" #event="eventProps">
224
+ <slot name="event" v-bind="eventProps" />
225
+ </template>
226
+ <template v-if="slots['day-header']" #day-header="dayHeaderProps">
227
+ <slot name="day-header" v-bind="dayHeaderProps" />
228
+ </template>
229
+ <template v-if="slots.day" #day="dayProps">
230
+ <slot name="day" v-bind="dayProps" />
231
+ </template>
232
+ <template v-if="slots['more-events']" #more-events="moreProps">
233
+ <slot name="more-events" v-bind="moreProps" />
234
+ </template>
235
+ <template v-if="slots.loading" #loading>
236
+ <slot name="loading" />
237
+ </template>
238
+ <template v-if="slots.empty" #empty>
239
+ <slot name="empty" />
240
+ </template>
241
+ </EventCalendarMonthView>
592
242
 
593
- <!-- Day columns -->
594
- <div
595
- v-for="day in timeGridDays"
596
- :key="`col-${day.dateKey}`"
597
- data-slot="dayColumn"
598
- :class="ui.dayColumn({ class: propUi?.dayColumn })"
599
- @dragover="onTimeGridDragOver(`time-${day.dateKey}`, $event)"
600
- @dragleave="onDragLeave"
601
- @drop="onDropTimeGrid(day.date, $event)">
602
- <!-- Time slot rows (grid lines + drag targets) -->
603
- <div
604
- v-for="slot in timeGridSlots"
605
- :key="`row-${slot.hour}-${slot.minute}`"
606
- data-slot="timeSlotRow"
607
- :class="[
608
- ui.timeSlotRow({ class: propUi?.timeSlotRow }),
609
- dragSnapSlot === `time-${day.dateKey}-${slot.hour}-${slot.minute}` && ui.dragSnapSlot({ class: propUi?.dragSnapSlot })
610
- ]"
611
- :style="{ height: `${SLOT_HEIGHT}px` }"
612
- @click="handleDateClick(day)"
613
- @dragover="onSlotDragOver(`time-${day.dateKey}-${slot.hour}-${slot.minute}`, $event)" />
243
+ <!-- Week/Day time grid view -->
244
+ <EventCalendarTimeGrid v-else-if="ctx.view.value === 'week' || ctx.view.value === 'day'">
245
+ <template v-if="slots.event" #event="eventProps">
246
+ <slot name="event" v-bind="eventProps" />
247
+ </template>
248
+ <template v-if="slots['time-label']" #time-label="timeLabelProps">
249
+ <slot name="time-label" v-bind="timeLabelProps" />
250
+ </template>
251
+ <template v-if="slots['all-day']" #all-day="allDayProps">
252
+ <slot name="all-day" v-bind="allDayProps" />
253
+ </template>
254
+ <template v-if="slots.loading" #loading>
255
+ <slot name="loading" />
256
+ </template>
257
+ <template v-if="slots.empty" #empty>
258
+ <slot name="empty" />
259
+ </template>
260
+ </EventCalendarTimeGrid>
614
261
 
615
- <!-- Timed events (absolutely positioned) -->
616
- <div
617
- v-for="event in day.timedEvents"
618
- :key="`ev-${event.id}`"
619
- data-slot="timeEvent"
620
- :draggable="editable && event.draggable"
621
- :class="[
622
- ui.timeEvent({ class: propUi?.timeEvent }),
623
- draggedEventId != null && draggedEventId !== event.id && 'pointer-events-none'
624
- ]"
625
- :style="{
626
- ...getEventStyle(event, 15),
627
- top: `${event.topPx}px`,
628
- height: `${event.heightPx}px`,
629
- minHeight: '20px',
630
- left: `calc(${event.column / event.totalColumns * 100}% + 2px)`,
631
- width: `calc(${1 / event.totalColumns * 100}% - 4px)`
632
- }"
633
- @click="handleEventClick(event, $event)"
634
- @dragstart="onDragStart(event, $event)"
635
- @dragend="onDragEnd">
636
- <slot name="event" :event="event.original" :view="view">
637
- <div data-slot="timeEventTitle" :class="ui.timeEventTitle({ class: propUi?.timeEventTitle })">
638
- {{ event.title }}
639
- </div>
640
- <div data-slot="timeEventTime" :class="ui.timeEventTime({ class: propUi?.timeEventTime })">
641
- {{ formatTime(event.start) }}
642
- <template v-if="timeGridDays.length === 1"> – {{ formatTime(event.end) }}</template>
643
- </div>
644
- </slot>
645
- </div>
646
- </div>
647
- </div>
262
+ <!-- List view -->
263
+ <EventCalendarListView v-else-if="ctx.view.value === 'list'">
264
+ <template v-if="slots.event" #event="eventProps">
265
+ <slot name="event" v-bind="eventProps" />
266
+ </template>
267
+ <template v-if="slots['date-header']" #date-header="dateHeaderProps">
268
+ <slot name="date-header" v-bind="dateHeaderProps" />
269
+ </template>
270
+ </EventCalendarListView>
648
271
  </template>
649
- </div>
272
+ </Primitive>
650
273
  </template>