nuxt-ui-elements-pro 0.1.4 → 0.1.6

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 (39) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +50 -15
  3. package/dist/runtime/components/EventCalendar.d.vue.ts +48 -42
  4. package/dist/runtime/components/EventCalendar.vue +116 -606
  5. package/dist/runtime/components/EventCalendar.vue.d.ts +48 -42
  6. package/dist/runtime/components/EventCalendarHeader.d.vue.ts +26 -0
  7. package/dist/runtime/components/EventCalendarHeader.vue +48 -0
  8. package/dist/runtime/components/EventCalendarHeader.vue.d.ts +26 -0
  9. package/dist/runtime/components/EventCalendarListView.d.vue.ts +25 -0
  10. package/dist/runtime/components/EventCalendarListView.vue +95 -0
  11. package/dist/runtime/components/EventCalendarListView.vue.d.ts +25 -0
  12. package/dist/runtime/components/EventCalendarMonthView.d.vue.ts +34 -0
  13. package/dist/runtime/components/EventCalendarMonthView.vue +336 -0
  14. package/dist/runtime/components/EventCalendarMonthView.vue.d.ts +34 -0
  15. package/dist/runtime/components/EventCalendarTimeGrid.d.vue.ts +31 -0
  16. package/dist/runtime/components/EventCalendarTimeGrid.vue +306 -0
  17. package/dist/runtime/components/EventCalendarTimeGrid.vue.d.ts +31 -0
  18. package/dist/runtime/composables/useEventCalendar.d.ts +52 -0
  19. package/dist/runtime/composables/useEventCalendar.js +362 -0
  20. package/dist/runtime/composables/useEventCalendarContext.d.ts +8 -0
  21. package/dist/runtime/composables/useEventCalendarContext.js +11 -0
  22. package/dist/runtime/composables/useEventCalendarDragDrop.d.ts +1 -1
  23. package/dist/runtime/composables/useEventCalendarDragDrop.js +11 -9
  24. package/dist/runtime/composables/useEventCalendarKeyboard.d.ts +20 -0
  25. package/dist/runtime/composables/useEventCalendarKeyboard.js +128 -0
  26. package/dist/runtime/composables/useEventCalendarResize.d.ts +31 -0
  27. package/dist/runtime/composables/useEventCalendarResize.js +87 -0
  28. package/dist/runtime/composables/useEventCalendarSelect.d.ts +21 -0
  29. package/dist/runtime/composables/useEventCalendarSelect.js +119 -0
  30. package/dist/runtime/index.d.ts +2 -0
  31. package/dist/runtime/index.js +1 -0
  32. package/dist/runtime/types/event-calendar.d.ts +169 -0
  33. package/dist/runtime/types/index.d.ts +4 -0
  34. package/dist/runtime/types/index.js +4 -0
  35. package/dist/runtime/utils/event-calendar.d.ts +22 -1
  36. package/dist/runtime/utils/event-calendar.js +199 -1
  37. package/dist/runtime/utils/recurrence.d.ts +30 -0
  38. package/dist/runtime/utils/recurrence.js +150 -0
  39. package/package.json +15 -6
@@ -1,4 +1,12 @@
1
- import { toCalendarDateTime } from "@internationalized/date";
1
+ import { getDayOfWeek, toCalendarDate, toCalendarDateTime } from "@internationalized/date";
2
+ export function startOfWeekByDay(date, weekStartsOn) {
3
+ const dow = getDayOfWeek(date, "en-US");
4
+ const diff = (dow - weekStartsOn + 7) % 7;
5
+ return toCalendarDate(date.subtract({ days: diff }));
6
+ }
7
+ export function endOfWeekByDay(date, weekStartsOn) {
8
+ return toCalendarDate(startOfWeekByDay(date, weekStartsOn).add({ days: 6 }));
9
+ }
2
10
  export function hasTimeComponent(d) {
3
11
  return "hour" in d;
4
12
  }
@@ -65,3 +73,193 @@ export function layoutTimedEvents(events, config, slotHeight) {
65
73
  }
66
74
  return timed;
67
75
  }
76
+ export function layoutMonthWeekEvents(week, maxEvents) {
77
+ const days = week.days;
78
+ const weekStartDate = days[0].date;
79
+ const weekEndDate = days[6].date;
80
+ const seenSpanning = /* @__PURE__ */ new Set();
81
+ const spanningCandidates = [];
82
+ const singleDayByCol = Array.from({ length: 7 }, () => []);
83
+ for (let col = 0; col < 7; col++) {
84
+ const day = days[col];
85
+ for (const event of day.events) {
86
+ const eventStartDate = toCalendarDate(event.start);
87
+ const eventEndDate = toCalendarDate(event.end);
88
+ const isMultiDay = eventStartDate.compare(eventEndDate) !== 0;
89
+ const isSpanning = event.allDay || isMultiDay;
90
+ if (isSpanning) {
91
+ if (!seenSpanning.has(event.id)) {
92
+ seenSpanning.add(event.id);
93
+ const clampedStart = eventStartDate.compare(weekStartDate) < 0 ? weekStartDate : eventStartDate;
94
+ const clampedEnd = eventEndDate.compare(weekEndDate) > 0 ? weekEndDate : eventEndDate;
95
+ let startCol = 0;
96
+ let endCol = 6;
97
+ for (let i = 0; i < 7; i++) {
98
+ if (days[i].date.compare(clampedStart) === 0) startCol = i;
99
+ if (days[i].date.compare(clampedEnd) === 0) endCol = i;
100
+ }
101
+ spanningCandidates.push({
102
+ event,
103
+ startCol,
104
+ endCol,
105
+ isStart: eventStartDate.compare(weekStartDate) >= 0,
106
+ isEnd: eventEndDate.compare(weekEndDate) <= 0
107
+ });
108
+ }
109
+ } else {
110
+ singleDayByCol[col].push(event);
111
+ }
112
+ }
113
+ }
114
+ spanningCandidates.sort((a, b) => {
115
+ if (a.startCol !== b.startCol) return a.startCol - b.startCol;
116
+ const spanA = a.endCol - a.startCol;
117
+ const spanB = b.endCol - b.startCol;
118
+ if (spanA !== spanB) return spanB - spanA;
119
+ return String(a.event.id).localeCompare(String(b.event.id));
120
+ });
121
+ const laneOccupancy = [];
122
+ const spanning = [];
123
+ for (const candidate of spanningCandidates) {
124
+ const { event, startCol, endCol, isStart, isEnd } = candidate;
125
+ const span = endCol - startCol + 1;
126
+ let assignedLane = -1;
127
+ for (let lane = 0; lane < laneOccupancy.length; lane++) {
128
+ let free = true;
129
+ for (let col = startCol; col <= endCol; col++) {
130
+ if (laneOccupancy[lane][col]) {
131
+ free = false;
132
+ break;
133
+ }
134
+ }
135
+ if (free) {
136
+ assignedLane = lane;
137
+ break;
138
+ }
139
+ }
140
+ if (assignedLane === -1) {
141
+ assignedLane = laneOccupancy.length;
142
+ laneOccupancy.push(new Array(7).fill(false));
143
+ }
144
+ for (let col = startCol; col <= endCol; col++) {
145
+ laneOccupancy[assignedLane][col] = true;
146
+ }
147
+ spanning.push({ event, startCol, span, lane: assignedLane, isStart, isEnd });
148
+ }
149
+ const perColSpanningOffset = new Array(7).fill(0);
150
+ for (let lane = 0; lane < laneOccupancy.length; lane++) {
151
+ for (let col = 0; col < 7; col++) {
152
+ if (laneOccupancy[lane][col]) {
153
+ perColSpanningOffset[col] = lane + 1;
154
+ }
155
+ }
156
+ }
157
+ const perColVisibleOffset = perColSpanningOffset.map((o) => Math.min(o, maxEvents));
158
+ const singleDay = [];
159
+ const overflow = [];
160
+ let totalEventRows = 0;
161
+ for (let col = 0; col < 7; col++) {
162
+ const colSingleDay = singleDayByCol[col];
163
+ const colOffset = perColVisibleOffset[col];
164
+ const colRemainingSlots = Math.max(0, maxEvents - colOffset);
165
+ const visibleSingle = colSingleDay.slice(0, colRemainingSlots);
166
+ for (let row = 0; row < visibleSingle.length; row++) {
167
+ singleDay.push({ event: visibleSingle[row], col, row, gridRow: colOffset + row + 2 });
168
+ }
169
+ totalEventRows = Math.max(totalEventRows, colOffset + visibleSingle.length);
170
+ const hiddenSpanning = [];
171
+ for (const s of spanning) {
172
+ if (s.lane >= maxEvents && s.startCol <= col && s.startCol + s.span - 1 >= col) {
173
+ hiddenSpanning.push(s.event);
174
+ }
175
+ }
176
+ const hiddenSingle = colSingleDay.slice(colRemainingSlots);
177
+ const hiddenCount = hiddenSpanning.length + hiddenSingle.length;
178
+ if (hiddenCount > 0) {
179
+ overflow.push({
180
+ col,
181
+ hiddenCount,
182
+ hiddenEvents: [...hiddenSpanning, ...hiddenSingle],
183
+ gridRow: 0
184
+ // set after loop
185
+ });
186
+ }
187
+ }
188
+ for (const item of overflow) {
189
+ item.gridRow = totalEventRows + 2;
190
+ }
191
+ return {
192
+ week,
193
+ spanning,
194
+ singleDay,
195
+ overflow,
196
+ totalEventRows,
197
+ spanningLaneCount: Math.min(laneOccupancy.length, maxEvents)
198
+ };
199
+ }
200
+ export function layoutAllDayEvents(days) {
201
+ const colCount = days.length;
202
+ if (colCount === 0) return { spanning: [], laneCount: 0 };
203
+ const startDate = days[0].date;
204
+ const endDate = days[colCount - 1].date;
205
+ const seen = /* @__PURE__ */ new Set();
206
+ const candidates = [];
207
+ for (let col = 0; col < colCount; col++) {
208
+ for (const event of days[col].allDayEvents) {
209
+ if (seen.has(event.id)) continue;
210
+ seen.add(event.id);
211
+ const eventStartDate = toCalendarDate(event.start);
212
+ const eventEndDate = toCalendarDate(event.end);
213
+ const clampedStart = eventStartDate.compare(startDate) < 0 ? startDate : eventStartDate;
214
+ const clampedEnd = eventEndDate.compare(endDate) > 0 ? endDate : eventEndDate;
215
+ let sc = 0;
216
+ let ec = colCount - 1;
217
+ for (let i = 0; i < colCount; i++) {
218
+ if (days[i].date.compare(clampedStart) === 0) sc = i;
219
+ if (days[i].date.compare(clampedEnd) === 0) ec = i;
220
+ }
221
+ candidates.push({
222
+ event,
223
+ startCol: sc,
224
+ endCol: ec,
225
+ isStart: eventStartDate.compare(startDate) >= 0,
226
+ isEnd: eventEndDate.compare(endDate) <= 0
227
+ });
228
+ }
229
+ }
230
+ candidates.sort((a, b) => {
231
+ if (a.startCol !== b.startCol) return a.startCol - b.startCol;
232
+ const spanA = a.endCol - a.startCol;
233
+ const spanB = b.endCol - b.startCol;
234
+ if (spanA !== spanB) return spanB - spanA;
235
+ return String(a.event.id).localeCompare(String(b.event.id));
236
+ });
237
+ const laneOccupancy = [];
238
+ const spanning = [];
239
+ for (const { event, startCol, endCol, isStart, isEnd } of candidates) {
240
+ const span = endCol - startCol + 1;
241
+ let assignedLane = -1;
242
+ for (let lane = 0; lane < laneOccupancy.length; lane++) {
243
+ let free = true;
244
+ for (let col = startCol; col <= endCol; col++) {
245
+ if (laneOccupancy[lane][col]) {
246
+ free = false;
247
+ break;
248
+ }
249
+ }
250
+ if (free) {
251
+ assignedLane = lane;
252
+ break;
253
+ }
254
+ }
255
+ if (assignedLane === -1) {
256
+ assignedLane = laneOccupancy.length;
257
+ laneOccupancy.push(new Array(colCount).fill(false));
258
+ }
259
+ for (let col = startCol; col <= endCol; col++) {
260
+ laneOccupancy[assignedLane][col] = true;
261
+ }
262
+ spanning.push({ event, startCol, span, lane: assignedLane, isStart, isEnd });
263
+ }
264
+ return { spanning, laneCount: laneOccupancy.length };
265
+ }
@@ -0,0 +1,30 @@
1
+ import type { CalendarDate } from "@internationalized/date";
2
+ import type { CalendarEvent } from "../types/event-calendar.js";
3
+ /** Visible date range for the calendar grid */
4
+ export interface VisibleRange {
5
+ start: CalendarDate;
6
+ end: CalendarDate;
7
+ }
8
+ /**
9
+ * Compute the date range the calendar grid actually renders.
10
+ * Includes padding for multi-day event bleed.
11
+ */
12
+ export declare function getVisibleRange(displayDate: CalendarDate, view: "month" | "week" | "day" | "list", weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6, fixedWeeks: boolean): VisibleRange;
13
+ /**
14
+ * Expand recurring events within a visible date range.
15
+ * Synchronous — requires the rrule module to be pre-loaded.
16
+ *
17
+ * @param events - Array of CalendarEvent (recurring and non-recurring mixed)
18
+ * @param range - Visible date range to expand within
19
+ * @param rruleLib - The pre-loaded rrule module (`import('rrule')`)
20
+ * @returns Flat array of CalendarEvent with occurrences expanded
21
+ */
22
+ export declare function expandRecurringEvents(events: CalendarEvent[], range: VisibleRange, rruleLib: typeof import("rrule")): CalendarEvent[];
23
+ /**
24
+ * Async convenience wrapper for standalone use (auto-imports rrule).
25
+ *
26
+ * @param events - Array of CalendarEvent
27
+ * @param range - Visible date range
28
+ * @returns Flat array of CalendarEvent with occurrences expanded
29
+ */
30
+ export declare function expandRecurringEventsAsync(events: CalendarEvent[], range: VisibleRange): Promise<CalendarEvent[]>;
@@ -0,0 +1,150 @@
1
+ import {
2
+ asCalendarDate,
3
+ asCalendarDateTime,
4
+ startOf,
5
+ add,
6
+ subtract
7
+ } from "#std/date";
8
+ import { hasTimeComponent, startOfWeekByDay } from "./event-calendar.js";
9
+ export function getVisibleRange(displayDate, view, weekStartsOn, fixedWeeks) {
10
+ if (view === "month") {
11
+ const monthStart = startOf(displayDate, "month");
12
+ const monthEnd = asCalendarDate(add(startOf(displayDate, "month"), 1, "month"));
13
+ const monthEndDate = asCalendarDate(subtract(monthEnd, 1, "day"));
14
+ const gridStart = asCalendarDate(subtract(startOfWeekByDay(monthStart, weekStartsOn), 1, "day"));
15
+ const gridEnd = fixedWeeks ? asCalendarDate(add(startOfWeekByDay(monthStart, weekStartsOn), 42, "day")) : asCalendarDate(add(startOfWeekByDay(monthEndDate, weekStartsOn).add({ days: 6 }), 1, "day"));
16
+ return { start: gridStart, end: gridEnd };
17
+ }
18
+ if (view === "week" || view === "list") {
19
+ const weekStart = startOfWeekByDay(asCalendarDate(displayDate), weekStartsOn);
20
+ return {
21
+ start: asCalendarDate(subtract(weekStart, 1, "day")),
22
+ end: asCalendarDate(add(weekStart, 7, "day"))
23
+ };
24
+ }
25
+ return {
26
+ start: asCalendarDate(subtract(displayDate, 1, "day")),
27
+ end: asCalendarDate(add(displayDate, 1, "day"))
28
+ };
29
+ }
30
+ function toFloatingUTC(value) {
31
+ if (hasTimeComponent(value)) {
32
+ const dt = value;
33
+ return new Date(Date.UTC(dt.year, dt.month - 1, dt.day, dt.hour, dt.minute, dt.second ?? 0));
34
+ }
35
+ return new Date(Date.UTC(value.year, value.month - 1, value.day));
36
+ }
37
+ function fromFloatingUTC(date, withTime) {
38
+ const year = date.getUTCFullYear();
39
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
40
+ const day = String(date.getUTCDate()).padStart(2, "0");
41
+ if (withTime) {
42
+ const hour = String(date.getUTCHours()).padStart(2, "0");
43
+ const minute = String(date.getUTCMinutes()).padStart(2, "0");
44
+ return asCalendarDateTime(`${year}-${month}-${day}T${hour}:${minute}`);
45
+ }
46
+ return asCalendarDate(`${year}-${month}-${day}`);
47
+ }
48
+ function parseDateValue(input) {
49
+ if (typeof input === "object" && "calendar" in input) {
50
+ return input;
51
+ }
52
+ if (input instanceof Date) {
53
+ const iso = input.toISOString();
54
+ if (iso.endsWith("T00:00:00.000Z")) {
55
+ return asCalendarDate(input);
56
+ }
57
+ return asCalendarDateTime(input);
58
+ }
59
+ if (typeof input === "string") {
60
+ if (input.includes("T") || input.includes(" ")) {
61
+ try {
62
+ return asCalendarDateTime(input);
63
+ } catch {
64
+ return asCalendarDate(input);
65
+ }
66
+ }
67
+ return asCalendarDate(input);
68
+ }
69
+ return asCalendarDate(/* @__PURE__ */ new Date());
70
+ }
71
+ function formatDtstart(date, allDay) {
72
+ const y = date.getUTCFullYear();
73
+ const m = String(date.getUTCMonth() + 1).padStart(2, "0");
74
+ const d = String(date.getUTCDate()).padStart(2, "0");
75
+ if (allDay) {
76
+ return `DTSTART;VALUE=DATE:${y}${m}${d}`;
77
+ }
78
+ const hh = String(date.getUTCHours()).padStart(2, "0");
79
+ const mm = String(date.getUTCMinutes()).padStart(2, "0");
80
+ const ss = String(date.getUTCSeconds()).padStart(2, "0");
81
+ return `DTSTART:${y}${m}${d}T${hh}${mm}${ss}Z`;
82
+ }
83
+ export function expandRecurringEvents(events, range, rruleLib) {
84
+ return events.flatMap((event) => {
85
+ if (!event.rrule) return [event];
86
+ return expandSingleEvent(event, range, rruleLib);
87
+ });
88
+ }
89
+ function expandSingleEvent(event, range, rruleLib) {
90
+ const { rrulestr } = rruleLib;
91
+ const parsedStart = parseDateValue(event.start);
92
+ const isTimedEvent = hasTimeComponent(parsedStart);
93
+ const startAsUTC = toFloatingUTC(parsedStart);
94
+ let rruleString = event.rrule;
95
+ const hasExistingDtstart = /DTSTART[;:]/i.test(rruleString);
96
+ if (!hasExistingDtstart) {
97
+ const dtstart = formatDtstart(startAsUTC, !isTimedEvent);
98
+ if (!rruleString.startsWith("RRULE:")) {
99
+ rruleString = `RRULE:${rruleString}`;
100
+ }
101
+ rruleString = `${dtstart}
102
+ ${rruleString}`;
103
+ }
104
+ let ruleSet;
105
+ try {
106
+ const parsed = rrulestr(rruleString, { forceset: true });
107
+ ruleSet = parsed;
108
+ } catch (err) {
109
+ console.warn(`[UEEventCalendar] Failed to parse rrule for event "${event.id}":`, err);
110
+ return [event];
111
+ }
112
+ if (event.exdate?.length) {
113
+ for (const exd of event.exdate) {
114
+ const parsedEx = parseDateValue(exd);
115
+ ruleSet.exdate(toFloatingUTC(parsedEx));
116
+ }
117
+ }
118
+ const rangeStart = toFloatingUTC(range.start);
119
+ const rangeEnd = toFloatingUTC(range.end);
120
+ const occurrences = ruleSet.between(rangeStart, rangeEnd, true);
121
+ if (occurrences.length === 0) return [];
122
+ let durationMinutes = 0;
123
+ if (event.duration != null) {
124
+ durationMinutes = event.duration;
125
+ } else if (event.end) {
126
+ const parsedEnd = parseDateValue(event.end);
127
+ const endUTC = toFloatingUTC(parsedEnd);
128
+ durationMinutes = Math.round((endUTC.getTime() - startAsUTC.getTime()) / 6e4);
129
+ }
130
+ return occurrences.map((occDate) => {
131
+ const occStart = fromFloatingUTC(occDate, isTimedEvent);
132
+ const occEnd = durationMinutes > 0 ? fromFloatingUTC(new Date(occDate.getTime() + durationMinutes * 6e4), isTimedEvent) : occStart;
133
+ const occKey = isTimedEvent ? `${occStart.toString()}` : `${occStart.toString()}`;
134
+ return {
135
+ id: `${event.id}::${occKey}`,
136
+ title: event.title,
137
+ start: occStart,
138
+ end: occEnd,
139
+ color: event.color,
140
+ allDay: event.allDay,
141
+ draggable: event.draggable,
142
+ resizable: event.resizable,
143
+ recurringEventId: event.id
144
+ };
145
+ });
146
+ }
147
+ export async function expandRecurringEventsAsync(events, range) {
148
+ const rruleLib = await import("rrule");
149
+ return expandRecurringEvents(events, range, rruleLib);
150
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-ui-elements-pro",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Pro components for nuxt-ui-elements",
5
5
  "license": "UNLICENSED",
6
6
  "files": [
@@ -46,23 +46,32 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@nuxt/devtools": "3.2.1",
49
- "@nuxt/ui": "4.4.0",
50
49
  "@nuxt/eslint-config": "1.15.1",
51
50
  "@nuxt/module-builder": "1.0.2",
52
51
  "@nuxt/schema": "4.3.1",
53
52
  "@nuxt/test-utils": "4.0.0",
53
+ "@nuxt/ui": "4.4.0",
54
54
  "@types/node": "latest",
55
- "eslint": "10.0.0",
55
+ "@vue/test-utils": "2.4.6",
56
+ "eslint": "10.0.1",
56
57
  "eslint-config-prettier": "10.1.8",
57
58
  "eslint-plugin-prettier": "5.5.5",
59
+ "happy-dom": "20.7.0",
58
60
  "nuxt": "4.3.1",
59
61
  "prettier": "3.8.1",
62
+ "rrule": "2.8.1",
60
63
  "typescript": "5.9.3",
61
64
  "vitest": "4.0.18",
62
- "vue-tsc": "3.2.4"
65
+ "vue-tsc": "3.2.5"
63
66
  },
64
67
  "peerDependencies": {
65
- "@nuxt/ui": "^4.0.0"
68
+ "@nuxt/ui": "^4.0.0",
69
+ "rrule": "^2.8.0"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "rrule": {
73
+ "optional": true
74
+ }
66
75
  },
67
- "packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8"
76
+ "packageManager": "pnpm@10.30.1+sha512.3590e550d5384caa39bd5c7c739f72270234b2f6059e13018f975c313b1eb9fefcc09714048765d4d9efe961382c312e624572c0420762bdc5d5940cdf9be73a"
68
77
  }