nuxt-ui-elements-pro 0.1.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.
@@ -0,0 +1,97 @@
1
+ import type { CalendarDate, DateValue } from "@internationalized/date";
2
+ import type { AppConfig } from "@nuxt/schema";
3
+ import theme from "#build/ui-elements-pro/event-calendar";
4
+ import type { ComponentConfig } from "nuxt-ui-elements";
5
+ import type { CalendarEvent, CalendarDay, MonthViewOptions, WeekViewOptions, DayViewOptions, EventDropPayload } from "../types/event-calendar.js";
6
+ type EventCalendar = ComponentConfig<typeof theme, AppConfig, "eventCalendar">;
7
+ export interface EventCalendarProps {
8
+ /** Array of events to display */
9
+ events?: CalendarEvent[];
10
+ /** Currently displayed date. Controls which month/week/day is shown. Supports v-model. */
11
+ modelValue?: Date | string | DateValue;
12
+ /** Calendar view mode @defaultValue 'month' */
13
+ view?: "month" | "week" | "day";
14
+ /** Locale for day/month names @defaultValue 'en-US' */
15
+ locale?: string;
16
+ /** Day the week starts on @defaultValue 0 */
17
+ weekStartsOn?: 0 | 1;
18
+ /** Enable drag-and-drop globally @defaultValue true */
19
+ editable?: boolean;
20
+ /** Theme color for calendar chrome @defaultValue 'primary' */
21
+ color?: EventCalendar["variants"]["color"];
22
+ /** Month view options */
23
+ monthOptions?: MonthViewOptions;
24
+ /** Week view options */
25
+ weekOptions?: WeekViewOptions;
26
+ /** Day view options */
27
+ dayOptions?: DayViewOptions;
28
+ /** Slot class overrides */
29
+ ui?: EventCalendar["slots"];
30
+ }
31
+ export interface EventCalendarEmits {
32
+ /** Fires when the displayed date changes */
33
+ "update:modelValue": [value: CalendarDate];
34
+ /** Fires when the view changes */
35
+ "update:view": [value: "month" | "week" | "day"];
36
+ /** Fires when a date cell is clicked */
37
+ dateClick: [date: CalendarDate];
38
+ /** Fires when an event is clicked */
39
+ eventClick: [event: CalendarEvent];
40
+ /** Fires after drag-and-drop */
41
+ eventDrop: [payload: EventDropPayload];
42
+ }
43
+ export interface EventCalendarSlots {
44
+ header: (props: {
45
+ title: string;
46
+ prev: () => void;
47
+ next: () => void;
48
+ today: () => void;
49
+ currentDate: CalendarDate;
50
+ view: "month" | "week" | "day";
51
+ setView: (v: "month" | "week" | "day") => void;
52
+ }) => any;
53
+ "day-header": (props: {
54
+ day: string;
55
+ index: number;
56
+ }) => any;
57
+ day: (props: {
58
+ day: CalendarDay;
59
+ }) => any;
60
+ event: (props: {
61
+ event: CalendarEvent;
62
+ view: "month" | "week" | "day";
63
+ }) => any;
64
+ "more-events": (props: {
65
+ events: CalendarEvent[];
66
+ count: number;
67
+ day: CalendarDay;
68
+ }) => any;
69
+ "time-label": (props: {
70
+ hour: number;
71
+ label: string;
72
+ }) => any;
73
+ "all-day": (props: {
74
+ events: CalendarEvent[];
75
+ date: CalendarDate;
76
+ }) => any;
77
+ }
78
+ declare const _default: typeof __VLS_export;
79
+ export default _default;
80
+ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<EventCalendarProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
81
+ "update:modelValue": (value: CalendarDate) => any;
82
+ "update:view": (value: "day" | "week" | "month") => any;
83
+ dateClick: (date: CalendarDate) => any;
84
+ eventClick: (event: CalendarEvent) => any;
85
+ eventDrop: (payload: EventDropPayload) => any;
86
+ }, string, import("vue").PublicProps, Readonly<EventCalendarProps> & Readonly<{
87
+ "onUpdate:modelValue"?: ((value: CalendarDate) => any) | undefined;
88
+ "onUpdate:view"?: ((value: "day" | "week" | "month") => any) | undefined;
89
+ onDateClick?: ((date: CalendarDate) => any) | undefined;
90
+ onEventClick?: ((event: CalendarEvent) => any) | undefined;
91
+ onEventDrop?: ((payload: EventDropPayload) => any) | undefined;
92
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, EventCalendarSlots>;
93
+ type __VLS_WithSlots<T, S> = T & {
94
+ new (): {
95
+ $slots: S;
96
+ };
97
+ };
@@ -0,0 +1,24 @@
1
+ import type { CalendarDate } from "@internationalized/date";
2
+ import type { ComputedRef } from "vue";
3
+ import type { EventDropPayload, NormalizedEvent } from "../types/event-calendar.js";
4
+ export interface UseEventCalendarDragDropOptions {
5
+ /** Whether drag-and-drop is enabled (getter, since it's a destructured prop) */
6
+ editable: () => boolean;
7
+ /** Reactive list of normalized events to look up dragged event by ID */
8
+ normalizedEvents: ComputedRef<NormalizedEvent[]>;
9
+ /** Callback invoked when an event is dropped on a new target */
10
+ onEventDrop: (payload: EventDropPayload) => void;
11
+ }
12
+ export declare function useEventCalendarDragDrop(options: UseEventCalendarDragDropOptions): {
13
+ draggedEventId: import("vue").Ref<string | number | null, string | number | null>;
14
+ dropTargetKey: import("vue").Ref<string | null, string | null>;
15
+ dragSnapSlot: import("vue").Ref<string | null, string | null>;
16
+ onDragStart: (event: NormalizedEvent, e: DragEvent) => void;
17
+ onDragEnd: (e: DragEvent) => void;
18
+ onDragOver: (key: string, e: DragEvent) => void;
19
+ onDragLeave: () => void;
20
+ onTimeGridDragOver: (dateKey: string, e: DragEvent) => void;
21
+ onSlotDragOver: (slotKey: string, e: DragEvent) => void;
22
+ onDropMonthCell: (targetDate: CalendarDate, e: DragEvent) => void;
23
+ onDropTimeGrid: (targetDate: CalendarDate, e: DragEvent) => void;
24
+ };
@@ -0,0 +1,116 @@
1
+ import { toCalendarDate, toCalendarDateTime } from "@internationalized/date";
2
+ import { ref } from "vue";
3
+ import { add, asCalendarDate, asCalendarDateTime } from "#std/date";
4
+ import { hasTimeComponent } from "../utils/event-calendar.js";
5
+ export function useEventCalendarDragDrop(options) {
6
+ const { editable, normalizedEvents, onEventDrop } = options;
7
+ const draggedEventId = ref(null);
8
+ const dropTargetKey = ref(null);
9
+ const dragSnapSlot = ref(null);
10
+ function onDragStart(event, e) {
11
+ if (!editable() || !event.draggable) {
12
+ e.preventDefault();
13
+ return;
14
+ }
15
+ draggedEventId.value = event.id;
16
+ e.dataTransfer.effectAllowed = "move";
17
+ e.dataTransfer.setData("text/plain", String(event.id));
18
+ if (e.target instanceof HTMLElement) {
19
+ e.target.style.opacity = "0.5";
20
+ }
21
+ }
22
+ function onDragEnd(e) {
23
+ draggedEventId.value = null;
24
+ dropTargetKey.value = null;
25
+ dragSnapSlot.value = null;
26
+ if (e.target instanceof HTMLElement) {
27
+ e.target.style.opacity = "";
28
+ }
29
+ }
30
+ function onDragOver(key, e) {
31
+ if (!draggedEventId.value) return;
32
+ e.preventDefault();
33
+ e.dataTransfer.dropEffect = "move";
34
+ dropTargetKey.value = key;
35
+ }
36
+ function onDragLeave() {
37
+ dropTargetKey.value = null;
38
+ dragSnapSlot.value = null;
39
+ }
40
+ function onTimeGridDragOver(dateKey, e) {
41
+ if (!draggedEventId.value) return;
42
+ e.preventDefault();
43
+ e.dataTransfer.dropEffect = "move";
44
+ dropTargetKey.value = dateKey;
45
+ }
46
+ function onSlotDragOver(slotKey, e) {
47
+ if (!draggedEventId.value) return;
48
+ e.preventDefault();
49
+ e.dataTransfer.dropEffect = "move";
50
+ dragSnapSlot.value = slotKey;
51
+ }
52
+ function onDropMonthCell(targetDate, e) {
53
+ e.preventDefault();
54
+ dropTargetKey.value = null;
55
+ const eventId = draggedEventId.value;
56
+ if (eventId === null) return;
57
+ const event = normalizedEvents.value.find((ev) => ev.id === eventId);
58
+ if (!event) return;
59
+ const oldStartDate = toCalendarDate(event.start);
60
+ const dayDiff = targetDate.compare(oldStartDate);
61
+ if (dayDiff === 0) return;
62
+ const newStart = asCalendarDate(add(event.start, dayDiff, "day"));
63
+ const newEnd = asCalendarDate(add(event.end, dayDiff, "day"));
64
+ onEventDrop({
65
+ event: event.original,
66
+ oldStart: event.start,
67
+ oldEnd: event.end,
68
+ newStart: hasTimeComponent(event.start) ? asCalendarDateTime(newStart) : newStart,
69
+ newEnd: hasTimeComponent(event.end) ? asCalendarDateTime(newEnd) : newEnd
70
+ });
71
+ draggedEventId.value = null;
72
+ }
73
+ function onDropTimeGrid(targetDate, e) {
74
+ e.preventDefault();
75
+ const eventId = draggedEventId.value;
76
+ const snapKey = dragSnapSlot.value;
77
+ dropTargetKey.value = null;
78
+ dragSnapSlot.value = null;
79
+ if (eventId === null || !snapKey) return;
80
+ const event = normalizedEvents.value.find((ev) => ev.id === eventId);
81
+ if (!event) return;
82
+ const parts = snapKey.split("-");
83
+ const newHour = parseInt(parts[parts.length - 2]);
84
+ const newMinute = parseInt(parts[parts.length - 1]);
85
+ const oldStartDt = hasTimeComponent(event.start) ? event.start : toCalendarDateTime(event.start);
86
+ const oldEndDt = hasTimeComponent(event.end) ? event.end : toCalendarDateTime(event.end);
87
+ const durationMinutes = oldEndDt.hour * 60 + oldEndDt.minute - (oldStartDt.hour * 60 + oldStartDt.minute);
88
+ const targetMinutes = newHour * 60 + newMinute;
89
+ const endMinutes = targetMinutes + Math.max(durationMinutes, 15);
90
+ const endHour = Math.floor(endMinutes / 60);
91
+ const endMinute = endMinutes % 60;
92
+ const newStartDate = toCalendarDateTime(targetDate).set({ hour: newHour, minute: newMinute });
93
+ const newEndDate = toCalendarDateTime(targetDate).set({ hour: endHour, minute: endMinute });
94
+ onEventDrop({
95
+ event: event.original,
96
+ oldStart: event.start,
97
+ oldEnd: event.end,
98
+ newStart: newStartDate,
99
+ newEnd: newEndDate
100
+ });
101
+ draggedEventId.value = null;
102
+ }
103
+ return {
104
+ draggedEventId,
105
+ dropTargetKey,
106
+ dragSnapSlot,
107
+ onDragStart,
108
+ onDragEnd,
109
+ onDragOver,
110
+ onDragLeave,
111
+ onTimeGridDragOver,
112
+ onSlotDragOver,
113
+ onDropMonthCell,
114
+ onDropTimeGrid
115
+ };
116
+ }
@@ -0,0 +1 @@
1
+ @import "#build/ui-elements-pro.css";@source "./components";
@@ -0,0 +1 @@
1
+ export * from "./types/index.js";
@@ -0,0 +1 @@
1
+ export * from "./types/index.js";
@@ -0,0 +1,92 @@
1
+ import type { CalendarDate, CalendarDateTime, DateValue } from "@internationalized/date";
2
+ /**
3
+ * Represents an event on the calendar.
4
+ * Accepts flexible date inputs (Date, ISO string, or DateValue).
5
+ */
6
+ export interface CalendarEvent {
7
+ /** Unique identifier */
8
+ id: string | number;
9
+ /** Display title */
10
+ title: string;
11
+ /** Start date/time */
12
+ start: Date | string | DateValue;
13
+ /** End date/time. Defaults to same as start */
14
+ end?: Date | string | DateValue;
15
+ /** Nuxt UI theme color name (e.g., 'primary', 'success', 'error') */
16
+ color?: string;
17
+ /** Whether this is an all-day event */
18
+ allDay?: boolean;
19
+ /** Whether this event can be dragged. Defaults to true */
20
+ draggable?: boolean;
21
+ }
22
+ /** Internal normalized event with parsed dates */
23
+ export interface NormalizedEvent {
24
+ id: string | number;
25
+ title: string;
26
+ start: CalendarDate | CalendarDateTime;
27
+ end: CalendarDate | CalendarDateTime;
28
+ color: string;
29
+ allDay: boolean;
30
+ draggable: boolean;
31
+ original: CalendarEvent;
32
+ }
33
+ /** A single day cell in the calendar grid */
34
+ export interface CalendarDay {
35
+ date: CalendarDate;
36
+ isCurrentMonth: boolean;
37
+ isToday: boolean;
38
+ isWeekend: boolean;
39
+ events: NormalizedEvent[];
40
+ }
41
+ /** A week row in the month grid */
42
+ export interface CalendarWeek {
43
+ days: CalendarDay[];
44
+ }
45
+ /** A time slot row for week/day views */
46
+ export interface TimeSlot {
47
+ hour: number;
48
+ minute: number;
49
+ label: string;
50
+ }
51
+ /** Month view configuration */
52
+ export interface MonthViewOptions {
53
+ /** Max event chips per cell before "+N more" @defaultValue 3 */
54
+ maxEvents?: number;
55
+ /** Always show 6 rows for consistent height @defaultValue true */
56
+ fixedWeeks?: boolean;
57
+ }
58
+ /** Week view configuration */
59
+ export interface WeekViewOptions {
60
+ /** First visible hour (0-23) @defaultValue 7 */
61
+ startHour?: number;
62
+ /** Last visible hour (0-23) @defaultValue 22 */
63
+ endHour?: number;
64
+ /** Minutes per time slot row @defaultValue 30 */
65
+ slotDuration?: 15 | 30 | 60;
66
+ }
67
+ /** Day view configuration */
68
+ export interface DayViewOptions {
69
+ /** First visible hour (0-23) @defaultValue 7 */
70
+ startHour?: number;
71
+ /** Last visible hour (0-23) @defaultValue 22 */
72
+ endHour?: number;
73
+ /** Minutes per time slot row @defaultValue 30 */
74
+ slotDuration?: 15 | 30 | 60;
75
+ }
76
+ /** A timed event with computed pixel positions and overlap column info */
77
+ export interface PositionedEvent extends NormalizedEvent {
78
+ topPx: number;
79
+ heightPx: number;
80
+ startMinutes: number;
81
+ endMinutes: number;
82
+ column: number;
83
+ totalColumns: number;
84
+ }
85
+ /** Payload emitted after a drag-and-drop operation */
86
+ export interface EventDropPayload {
87
+ event: CalendarEvent;
88
+ oldStart: CalendarDate | CalendarDateTime;
89
+ oldEnd: CalendarDate | CalendarDateTime;
90
+ newStart: CalendarDate | CalendarDateTime;
91
+ newEnd: CalendarDate | CalendarDateTime;
92
+ }
File without changes
@@ -0,0 +1,2 @@
1
+ export * from "./event-calendar.js";
2
+ export * from "../components/EventCalendar.vue.js";
@@ -0,0 +1,2 @@
1
+ export * from "./event-calendar.js";
2
+ export * from "../components/EventCalendar.vue";
@@ -0,0 +1,16 @@
1
+ import type { CalendarDate, CalendarDateTime } from "@internationalized/date";
2
+ import type { NormalizedEvent, PositionedEvent } from "../types/event-calendar.js";
3
+ /** Check whether a DateValue has hour/minute (CalendarDateTime vs CalendarDate) */
4
+ export declare function hasTimeComponent(d: CalendarDate | CalendarDateTime): boolean;
5
+ /**
6
+ * Compute pixel positions and overlap columns for timed events in a single day.
7
+ *
8
+ * Filters the given events to timed-only (non-all-day with hour/minute),
9
+ * calculates vertical position and height based on the time grid configuration,
10
+ * then assigns columns so overlapping events sit side-by-side.
11
+ */
12
+ export declare function layoutTimedEvents(events: NormalizedEvent[], config: {
13
+ startHour: number;
14
+ endHour: number;
15
+ slotDuration: number;
16
+ }, slotHeight: number): PositionedEvent[];
@@ -0,0 +1,67 @@
1
+ import { toCalendarDateTime } from "@internationalized/date";
2
+ export function hasTimeComponent(d) {
3
+ return "hour" in d;
4
+ }
5
+ export function layoutTimedEvents(events, config, slotHeight) {
6
+ const timed = events.filter((e) => !e.allDay && hasTimeComponent(e.start)).map((event) => {
7
+ const startDt = event.start;
8
+ const endDt = hasTimeComponent(event.end) ? event.end : toCalendarDateTime(event.end);
9
+ const gridStartMinutes = config.startHour * 60;
10
+ const eventStartMinutes = startDt.hour * 60 + startDt.minute;
11
+ const eventEndMinutes = endDt.hour * 60 + endDt.minute;
12
+ const duration = Math.max(eventEndMinutes - eventStartMinutes, 15);
13
+ const topPx = (eventStartMinutes - gridStartMinutes) / config.slotDuration * slotHeight;
14
+ const heightPx = duration / config.slotDuration * slotHeight;
15
+ return {
16
+ ...event,
17
+ topPx: Math.max(0, topPx),
18
+ heightPx,
19
+ startMinutes: eventStartMinutes,
20
+ endMinutes: eventStartMinutes + duration,
21
+ column: 0,
22
+ totalColumns: 1
23
+ };
24
+ }).sort((a, b) => a.startMinutes - b.startMinutes || b.endMinutes - a.endMinutes || String(a.id).localeCompare(String(b.id)));
25
+ if (timed.length > 1) {
26
+ const clusters = [];
27
+ let currentCluster = [];
28
+ for (const ev of timed) {
29
+ if (currentCluster.length === 0) {
30
+ currentCluster.push(ev);
31
+ } else {
32
+ const clusterEnd = Math.max(...currentCluster.map((e) => e.endMinutes));
33
+ if (ev.startMinutes < clusterEnd) {
34
+ currentCluster.push(ev);
35
+ } else {
36
+ clusters.push(currentCluster);
37
+ currentCluster = [ev];
38
+ }
39
+ }
40
+ }
41
+ if (currentCluster.length > 0) clusters.push(currentCluster);
42
+ for (const cluster of clusters) {
43
+ const columns = [];
44
+ for (const ev of cluster) {
45
+ let placed = false;
46
+ for (let col = 0; col < columns.length; col++) {
47
+ const lastInCol = columns[col][columns[col].length - 1];
48
+ if (lastInCol.endMinutes <= ev.startMinutes) {
49
+ columns[col].push(ev);
50
+ ev.column = col;
51
+ placed = true;
52
+ break;
53
+ }
54
+ }
55
+ if (!placed) {
56
+ ev.column = columns.length;
57
+ columns.push([ev]);
58
+ }
59
+ }
60
+ const totalCols = columns.length;
61
+ for (const ev of cluster) {
62
+ ev.totalColumns = totalCols;
63
+ }
64
+ }
65
+ }
66
+ return timed;
67
+ }
@@ -0,0 +1 @@
1
+ export declare const tv: import("tailwind-variants").TV;
@@ -0,0 +1,2 @@
1
+ import { createTV } from "tailwind-variants";
2
+ export const tv = /* @__PURE__ */ createTV({});
@@ -0,0 +1,5 @@
1
+ export { default } from './module.mjs'
2
+
3
+ export { type ModuleOptions } from './module.mjs'
4
+
5
+ export * from '../dist/runtime/types/index.js'
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "nuxt-ui-elements-pro",
3
+ "version": "0.1.0",
4
+ "description": "Pro components for nuxt-ui-elements",
5
+ "license": "UNLICENSED",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "main": "./dist/module.mjs",
11
+ "style": "./dist/runtime/index.css",
12
+ "typesVersions": {
13
+ "*": {
14
+ ".": [
15
+ "./dist/types.d.mts"
16
+ ]
17
+ }
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/types.d.mts",
22
+ "style": "./dist/runtime/index.css",
23
+ "import": "./dist/module.mjs"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "prepack": "nuxt-module-build build",
28
+ "dev": "pnpm dev:prepare && nuxi dev playground",
29
+ "dev:build": "nuxi build playground",
30
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
31
+ "lint": "eslint .",
32
+ "lint:fix": "eslint . --fix",
33
+ "format": "prettier --write .",
34
+ "format:check": "prettier --check .",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest watch",
37
+ "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
38
+ },
39
+ "dependencies": {
40
+ "@internationalized/date": "3.11.0",
41
+ "@nuxt/kit": "4.3.1",
42
+ "nuxt-ui-elements": "0.1.39",
43
+ "scule": "1.3.0",
44
+ "tailwind-variants": "3.2.2"
45
+ },
46
+ "devDependencies": {
47
+ "@nuxt/devtools": "3.2.1",
48
+ "@nuxt/ui": "4.4.0",
49
+ "@nuxt/eslint-config": "1.15.1",
50
+ "@nuxt/module-builder": "1.0.2",
51
+ "@nuxt/schema": "4.3.1",
52
+ "@nuxt/test-utils": "4.0.0",
53
+ "@types/node": "latest",
54
+ "eslint": "10.0.0",
55
+ "eslint-config-prettier": "10.1.8",
56
+ "eslint-plugin-prettier": "5.5.5",
57
+ "nuxt": "4.3.1",
58
+ "prettier": "3.8.1",
59
+ "typescript": "5.9.3",
60
+ "vitest": "4.0.18",
61
+ "vue-tsc": "3.2.4"
62
+ },
63
+ "peerDependencies": {
64
+ "@nuxt/ui": "^4.0.0",
65
+ "nuxt-ui-elements": ">=0.1.0"
66
+ },
67
+ "packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8"
68
+ }