nuxt-ui-elements-pro 0.1.9 → 0.1.11

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 (77) hide show
  1. package/dist/module.d.mts +5 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +243 -3
  4. package/dist/runtime/components/EventCalendar.d.vue.ts +4 -0
  5. package/dist/runtime/components/EventCalendar.vue +5 -2
  6. package/dist/runtime/components/EventCalendar.vue.d.ts +4 -0
  7. package/dist/runtime/components/FeedbackWidget.d.vue.ts +118 -0
  8. package/dist/runtime/components/FeedbackWidget.vue +141 -0
  9. package/dist/runtime/components/FeedbackWidget.vue.d.ts +118 -0
  10. package/dist/runtime/components/GanttChart.d.vue.ts +138 -0
  11. package/dist/runtime/components/GanttChart.vue +206 -0
  12. package/dist/runtime/components/GanttChart.vue.d.ts +138 -0
  13. package/dist/runtime/components/event-calendar/EventCalendarMonthView.d.vue.ts +3 -0
  14. package/dist/runtime/components/event-calendar/EventCalendarMonthView.vue +15 -6
  15. package/dist/runtime/components/event-calendar/EventCalendarMonthView.vue.d.ts +3 -0
  16. package/dist/runtime/components/event-calendar/EventCalendarTimeGrid.vue +23 -3
  17. package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.d.vue.ts +22 -0
  18. package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.vue +27 -0
  19. package/dist/runtime/components/feedback-widget/FeedbackWidgetButton.vue.d.ts +22 -0
  20. package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.d.vue.ts +42 -0
  21. package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.vue +288 -0
  22. package/dist/runtime/components/feedback-widget/FeedbackWidgetPanel.vue.d.ts +42 -0
  23. package/dist/runtime/components/gantt-chart/GanttChartDependencies.d.vue.ts +16 -0
  24. package/dist/runtime/components/gantt-chart/GanttChartDependencies.vue +70 -0
  25. package/dist/runtime/components/gantt-chart/GanttChartDependencies.vue.d.ts +16 -0
  26. package/dist/runtime/components/gantt-chart/GanttChartHeader.d.vue.ts +41 -0
  27. package/dist/runtime/components/gantt-chart/GanttChartHeader.vue +56 -0
  28. package/dist/runtime/components/gantt-chart/GanttChartHeader.vue.d.ts +41 -0
  29. package/dist/runtime/components/gantt-chart/GanttChartTimeline.d.vue.ts +34 -0
  30. package/dist/runtime/components/gantt-chart/GanttChartTimeline.vue +193 -0
  31. package/dist/runtime/components/gantt-chart/GanttChartTimeline.vue.d.ts +34 -0
  32. package/dist/runtime/composables/useEventCalendar.d.ts +2 -0
  33. package/dist/runtime/composables/useEventCalendar.js +8 -6
  34. package/dist/runtime/composables/useEventCalendarContext.d.ts +2 -7
  35. package/dist/runtime/composables/useEventCalendarContext.js +4 -11
  36. package/dist/runtime/composables/useFeedbackWidget.d.ts +46 -0
  37. package/dist/runtime/composables/useFeedbackWidget.js +137 -0
  38. package/dist/runtime/composables/useFeedbackWidgetContext.d.ts +3 -0
  39. package/dist/runtime/composables/useFeedbackWidgetContext.js +4 -0
  40. package/dist/runtime/composables/useGanttChart.d.ts +52 -0
  41. package/dist/runtime/composables/useGanttChart.js +224 -0
  42. package/dist/runtime/composables/useGanttChartContext.d.ts +3 -0
  43. package/dist/runtime/composables/useGanttChartContext.js +4 -0
  44. package/dist/runtime/composables/useGanttChartDragDrop.d.ts +28 -0
  45. package/dist/runtime/composables/useGanttChartDragDrop.js +68 -0
  46. package/dist/runtime/composables/useGanttChartKeyboard.d.ts +14 -0
  47. package/dist/runtime/composables/useGanttChartKeyboard.js +92 -0
  48. package/dist/runtime/composables/useGanttChartResize.d.ts +26 -0
  49. package/dist/runtime/composables/useGanttChartResize.js +89 -0
  50. package/dist/runtime/composables/useOrgChartContext.d.ts +2 -7
  51. package/dist/runtime/composables/useOrgChartContext.js +4 -11
  52. package/dist/runtime/index.d.ts +1 -0
  53. package/dist/runtime/index.js +1 -0
  54. package/dist/runtime/server/api/_feedback.post.d.ts +3 -0
  55. package/dist/runtime/server/api/_feedback.post.js +115 -0
  56. package/dist/runtime/server/nodemailer.d.ts +29 -0
  57. package/dist/runtime/server/utils/feedback-captcha.d.ts +1 -0
  58. package/dist/runtime/server/utils/feedback-captcha.js +27 -0
  59. package/dist/runtime/server/utils/feedback-rate-limit.d.ts +4 -0
  60. package/dist/runtime/server/utils/feedback-rate-limit.js +26 -0
  61. package/dist/runtime/types/event-calendar.d.ts +10 -4
  62. package/dist/runtime/types/feedback-widget.d.ts +110 -0
  63. package/dist/runtime/types/feedback-widget.js +0 -0
  64. package/dist/runtime/types/gantt-chart.d.ts +223 -0
  65. package/dist/runtime/types/gantt-chart.js +0 -0
  66. package/dist/runtime/types/index.d.ts +4 -0
  67. package/dist/runtime/types/index.js +4 -0
  68. package/dist/runtime/utils/createComponentContext.d.ts +15 -0
  69. package/dist/runtime/utils/createComponentContext.js +17 -0
  70. package/dist/runtime/utils/date.d.ts +10 -0
  71. package/dist/runtime/utils/date.js +9 -0
  72. package/dist/runtime/utils/event-calendar.d.ts +1 -9
  73. package/dist/runtime/utils/event-calendar.js +2 -9
  74. package/dist/runtime/utils/gantt-chart.d.ts +85 -0
  75. package/dist/runtime/utils/gantt-chart.js +549 -0
  76. package/dist/runtime/utils/recurrence.js +2 -1
  77. package/package.json +17 -8
@@ -1,14 +1,6 @@
1
1
  import type { CalendarDate, CalendarDateTime } from "@internationalized/date";
2
2
  import type { NormalizedEvent, PositionedEvent, CalendarWeek, MonthWeekLayout, AllDayLayout, TimeGridDay } from "../types/event-calendar.js";
3
- /**
4
- * Compute the start of the week for any weekStartsOn value (0=Sunday … 6=Saturday).
5
- * Uses getDayOfWeek with a fixed locale ('en-US') so Sunday is always 0.
6
- */
7
- export declare function startOfWeekByDay(date: CalendarDate, weekStartsOn: number): CalendarDate;
8
- /**
9
- * Compute the end of the week for any weekStartsOn value.
10
- */
11
- export declare function endOfWeekByDay(date: CalendarDate, weekStartsOn: number): CalendarDate;
3
+ export { startOfWeekByDay, endOfWeekByDay } from "./date.js";
12
4
  /** Check whether a DateValue has hour/minute (CalendarDateTime vs CalendarDate) */
13
5
  export declare function hasTimeComponent(d: CalendarDate | CalendarDateTime): boolean;
14
6
  /**
@@ -1,12 +1,5 @@
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
- }
1
+ import { toCalendarDate, toCalendarDateTime } from "@internationalized/date";
2
+ export { startOfWeekByDay, endOfWeekByDay } from "./date.js";
10
3
  export function hasTimeComponent(d) {
11
4
  return "hour" in d;
12
5
  }
@@ -0,0 +1,85 @@
1
+ import { CalendarDate } from "@internationalized/date";
2
+ import type { NormalizedGanttTask, GanttTaskTreeNode, GanttTaskBar, GanttDependency, GanttDependencyPath, GanttTimelineColumn, GanttHeaderGroup, GanttLayout, GanttZoomLevel, GanttZoomPresets } from "../types/gantt-chart.js";
3
+ export declare const DEFAULT_ROW_HEIGHT = 40;
4
+ export declare const DEFAULT_ZOOM_PRESETS: GanttZoomPresets;
5
+ /** Count the number of days from `a` to `b` (b - a). Can be negative. */
6
+ export declare function daysBetween(a: CalendarDate, b: CalendarDate): number;
7
+ /** Approximate month difference (fractional) from `a` to `b`. */
8
+ export declare function monthsBetween(a: CalendarDate, b: CalendarDate): number;
9
+ /**
10
+ * Align a date to the start or end of the appropriate zoom boundary.
11
+ * E.g., for "month" zoom, aligns to the 1st of the month.
12
+ */
13
+ export declare function alignToZoomBoundary(date: CalendarDate, zoomLevel: GanttZoomLevel, edge: "start" | "end"): CalendarDate;
14
+ /**
15
+ * Convert a CalendarDate to pixel offset from the timeline start.
16
+ */
17
+ export declare function dateToPixel(date: CalendarDate, timelineStart: CalendarDate, zoomLevel: GanttZoomLevel, unitWidth: number): number;
18
+ /**
19
+ * Convert a pixel offset to a CalendarDate, snapped to the appropriate unit.
20
+ */
21
+ export declare function pixelToDate(px: number, timelineStart: CalendarDate, zoomLevel: GanttZoomLevel, unitWidth: number): CalendarDate;
22
+ /**
23
+ * Snap a pixel value to the nearest grid line for the given zoom level.
24
+ */
25
+ export declare function snapToGrid(px: number, zoomLevel: GanttZoomLevel, unitWidth: number): number;
26
+ /**
27
+ * Compute timeline start/end from tasks, with padding aligned to zoom boundaries.
28
+ */
29
+ export declare function computeTimelineBounds(tasks: NormalizedGanttTask[], zoomLevel: GanttZoomLevel): {
30
+ start: CalendarDate;
31
+ end: CalendarDate;
32
+ };
33
+ /**
34
+ * Generate timeline columns for the bottom header row.
35
+ */
36
+ export declare function generateColumns(start: CalendarDate, end: CalendarDate, zoomLevel: GanttZoomLevel, unitWidth: number, locale: string): GanttTimelineColumn[];
37
+ /**
38
+ * Generate header groups (top row above columns).
39
+ */
40
+ export declare function generateHeaderGroups(columns: GanttTimelineColumn[], zoomLevel: GanttZoomLevel, locale: string): GanttHeaderGroup[];
41
+ /**
42
+ * Build a forest of trees from a flat task list using parentId.
43
+ * Tasks with parentId === null/undefined are roots. Multiple roots are supported.
44
+ */
45
+ export declare function buildTaskTree(tasks: NormalizedGanttTask[]): GanttTaskTreeNode[];
46
+ /**
47
+ * Flatten the task tree into a visible list, respecting expanded state.
48
+ */
49
+ export declare function flattenVisibleTasks(tree: GanttTaskTreeNode[], expandedIds: Set<string | number>): {
50
+ task: NormalizedGanttTask;
51
+ depth: number;
52
+ isGroup: boolean;
53
+ isExpanded: boolean;
54
+ childCount: number;
55
+ }[];
56
+ /**
57
+ * Walk the tree bottom-up and set each group's start/end to the min/max
58
+ * of all its descendants. This ensures summary bars always encompass
59
+ * their children, matching the behavior of tools like MS Project.
60
+ */
61
+ export declare function computeGroupSpans(tree: GanttTaskTreeNode[]): void;
62
+ /**
63
+ * Collect all task IDs that have children (i.e., are groups).
64
+ */
65
+ export declare function getAllGroupIds(tree: GanttTaskTreeNode[]): Set<string | number>;
66
+ /**
67
+ * Compute SVG path data for Finish-to-Start dependency arrows.
68
+ */
69
+ export declare function computeDependencyPaths(bars: GanttTaskBar[], dependencies: GanttDependency[], rowHeight: number): GanttDependencyPath[];
70
+ export declare function buildDependencyPath(from: GanttTaskBar, to: GanttTaskBar, rowHeight: number): {
71
+ path: string;
72
+ fromPoint: {
73
+ x: number;
74
+ y: number;
75
+ };
76
+ toPoint: {
77
+ x: number;
78
+ y: number;
79
+ };
80
+ };
81
+ /**
82
+ * Compute the full Gantt chart layout: task bar positions, columns, headers,
83
+ * dependency paths, and today marker.
84
+ */
85
+ export declare function computeGanttLayout(tasks: NormalizedGanttTask[], dependencies: GanttDependency[], expandedIds: Set<string | number>, zoomLevel: GanttZoomLevel, unitWidth: number, rowHeight: number, locale: string): GanttLayout;
@@ -0,0 +1,549 @@
1
+ import { CalendarDate, getDayOfWeek, today, getLocalTimeZone } from "@internationalized/date";
2
+ import { startOfWeekByDay } from "./date.js";
3
+ export const DEFAULT_ROW_HEIGHT = 40;
4
+ export const DEFAULT_ZOOM_PRESETS = {
5
+ day: { unitWidth: 40 },
6
+ week: { unitWidth: 60 },
7
+ month: { unitWidth: 30 },
8
+ quarter: { unitWidth: 30 },
9
+ year: { unitWidth: 60 }
10
+ };
11
+ export function daysBetween(a, b) {
12
+ return b.compare(a);
13
+ }
14
+ export function monthsBetween(a, b) {
15
+ const yearDiff = b.year - a.year;
16
+ const monthDiff = b.month - a.month;
17
+ const dayFraction = (b.day - a.day) / 30;
18
+ return yearDiff * 12 + monthDiff + dayFraction;
19
+ }
20
+ function daysInMonth(year, month) {
21
+ return new CalendarDate(year, month, 1).add({ months: 1 }).subtract({ days: 1 }).day;
22
+ }
23
+ export function alignToZoomBoundary(date, zoomLevel, edge) {
24
+ switch (zoomLevel) {
25
+ case "day":
26
+ return edge === "start" ? date : date;
27
+ case "week": {
28
+ const weekStart = startOfWeekByDay(date, 1);
29
+ return edge === "start" ? weekStart : weekStart.add({ days: 6 });
30
+ }
31
+ case "month":
32
+ return edge === "start" ? new CalendarDate(date.year, date.month, 1) : new CalendarDate(date.year, date.month, daysInMonth(date.year, date.month));
33
+ case "quarter": {
34
+ const qStartMonth = Math.floor((date.month - 1) / 3) * 3 + 1;
35
+ if (edge === "start") {
36
+ return new CalendarDate(date.year, qStartMonth, 1);
37
+ }
38
+ const qEndMonth = qStartMonth + 2;
39
+ return new CalendarDate(date.year, qEndMonth, daysInMonth(date.year, qEndMonth));
40
+ }
41
+ case "year":
42
+ return edge === "start" ? new CalendarDate(date.year, 1, 1) : new CalendarDate(date.year, 12, 31);
43
+ }
44
+ }
45
+ export function dateToPixel(date, timelineStart, zoomLevel, unitWidth) {
46
+ switch (zoomLevel) {
47
+ case "day": {
48
+ const days = daysBetween(timelineStart, date);
49
+ return days * 24 * unitWidth;
50
+ }
51
+ case "week":
52
+ case "month": {
53
+ const days = daysBetween(timelineStart, date);
54
+ return days * unitWidth;
55
+ }
56
+ case "quarter": {
57
+ const days = daysBetween(timelineStart, date);
58
+ return days / 7 * unitWidth;
59
+ }
60
+ case "year": {
61
+ const months = monthsBetween(timelineStart, date);
62
+ return months * unitWidth;
63
+ }
64
+ }
65
+ }
66
+ export function pixelToDate(px, timelineStart, zoomLevel, unitWidth) {
67
+ switch (zoomLevel) {
68
+ case "day": {
69
+ const totalHours = px / unitWidth;
70
+ const days = Math.floor(totalHours / 24);
71
+ return timelineStart.add({ days });
72
+ }
73
+ case "week":
74
+ case "month": {
75
+ const days = Math.round(px / unitWidth);
76
+ return timelineStart.add({ days });
77
+ }
78
+ case "quarter": {
79
+ const weeks = px / unitWidth;
80
+ const days = Math.round(weeks * 7);
81
+ return timelineStart.add({ days });
82
+ }
83
+ case "year": {
84
+ const months = Math.round(px / unitWidth);
85
+ return timelineStart.add({ months });
86
+ }
87
+ }
88
+ }
89
+ export function snapToGrid(px, zoomLevel, unitWidth) {
90
+ switch (zoomLevel) {
91
+ case "day":
92
+ return Math.round(px / unitWidth) * unitWidth;
93
+ case "week":
94
+ case "month":
95
+ return Math.round(px / unitWidth) * unitWidth;
96
+ case "quarter":
97
+ return Math.round(px / unitWidth) * unitWidth;
98
+ case "year":
99
+ return Math.round(px / unitWidth) * unitWidth;
100
+ }
101
+ }
102
+ export function computeTimelineBounds(tasks, zoomLevel) {
103
+ if (tasks.length === 0) {
104
+ const now = today(getLocalTimeZone());
105
+ return {
106
+ start: alignToZoomBoundary(now.subtract({ months: 1 }), zoomLevel, "start"),
107
+ end: alignToZoomBoundary(now.add({ months: 1 }), zoomLevel, "end")
108
+ };
109
+ }
110
+ let minStart = tasks[0].start;
111
+ let maxEnd = tasks[0].end;
112
+ for (const task of tasks) {
113
+ if (task.start.compare(minStart) < 0) minStart = task.start;
114
+ if (task.end.compare(maxEnd) > 0) maxEnd = task.end;
115
+ }
116
+ const paddingMap = {
117
+ day: { days: 1 },
118
+ week: { days: 7 },
119
+ month: { days: 14 },
120
+ quarter: { months: 1 },
121
+ year: { months: 2 }
122
+ };
123
+ const padding = paddingMap[zoomLevel];
124
+ const paddedStart = padding.months ? minStart.subtract({ months: padding.months }) : minStart.subtract({ days: padding.days ?? 7 });
125
+ const paddedEnd = padding.months ? maxEnd.add({ months: padding.months }) : maxEnd.add({ days: padding.days ?? 7 });
126
+ return {
127
+ start: alignToZoomBoundary(paddedStart, zoomLevel, "start"),
128
+ end: alignToZoomBoundary(paddedEnd, zoomLevel, "end")
129
+ };
130
+ }
131
+ export function generateColumns(start, end, zoomLevel, unitWidth, locale) {
132
+ const columns = [];
133
+ const todayDate = today(getLocalTimeZone());
134
+ switch (zoomLevel) {
135
+ case "day": {
136
+ const hourFormatter = new Intl.DateTimeFormat(locale, { hour: "numeric" });
137
+ let cursor = start;
138
+ while (cursor.compare(end) <= 0) {
139
+ for (let hour = 0; hour < 24; hour++) {
140
+ const leftPx = dateToPixel(cursor, start, zoomLevel, unitWidth) + hour * unitWidth;
141
+ const label = hourFormatter.format(new Date(cursor.year, cursor.month - 1, cursor.day, hour));
142
+ columns.push({
143
+ date: cursor,
144
+ label,
145
+ widthPx: unitWidth,
146
+ leftPx,
147
+ isToday: cursor.compare(todayDate) === 0,
148
+ isWeekend: getDayOfWeek(cursor, "en-US") === 0 || getDayOfWeek(cursor, "en-US") === 6
149
+ });
150
+ }
151
+ cursor = cursor.add({ days: 1 });
152
+ }
153
+ break;
154
+ }
155
+ case "week":
156
+ case "month": {
157
+ let cursor = start;
158
+ let leftPx = 0;
159
+ while (cursor.compare(end) <= 0) {
160
+ const dow = getDayOfWeek(cursor, "en-US");
161
+ columns.push({
162
+ date: cursor,
163
+ label: zoomLevel === "week" ? new Intl.DateTimeFormat(locale, { weekday: "short" }).format(new Date(cursor.year, cursor.month - 1, cursor.day)) + " " + cursor.day : String(cursor.day),
164
+ widthPx: unitWidth,
165
+ leftPx,
166
+ isToday: cursor.compare(todayDate) === 0,
167
+ isWeekend: dow === 0 || dow === 6
168
+ });
169
+ cursor = cursor.add({ days: 1 });
170
+ leftPx += unitWidth;
171
+ }
172
+ break;
173
+ }
174
+ case "quarter": {
175
+ let cursor = startOfWeekByDay(start, 1);
176
+ let leftPx = 0;
177
+ while (cursor.compare(end) <= 0) {
178
+ columns.push({
179
+ date: cursor,
180
+ label: `W${getWeekNumber(cursor)}`,
181
+ widthPx: unitWidth,
182
+ leftPx,
183
+ isToday: cursor.compare(todayDate) <= 0 && cursor.add({ days: 6 }).compare(todayDate) >= 0,
184
+ isWeekend: false
185
+ });
186
+ cursor = cursor.add({ days: 7 });
187
+ leftPx += unitWidth;
188
+ }
189
+ break;
190
+ }
191
+ case "year": {
192
+ let cursor = new CalendarDate(start.year, start.month, 1);
193
+ let leftPx = 0;
194
+ while (cursor.compare(end) <= 0) {
195
+ const label = new Intl.DateTimeFormat(locale, { month: "short" }).format(
196
+ new Date(cursor.year, cursor.month - 1, 1)
197
+ );
198
+ columns.push({
199
+ date: cursor,
200
+ label,
201
+ widthPx: unitWidth,
202
+ leftPx,
203
+ isToday: cursor.year === todayDate.year && cursor.month === todayDate.month,
204
+ isWeekend: false
205
+ });
206
+ if (cursor.month === 12) {
207
+ cursor = new CalendarDate(cursor.year + 1, 1, 1);
208
+ } else {
209
+ cursor = new CalendarDate(cursor.year, cursor.month + 1, 1);
210
+ }
211
+ leftPx += unitWidth;
212
+ }
213
+ break;
214
+ }
215
+ }
216
+ return columns;
217
+ }
218
+ function getWeekNumber(date) {
219
+ const jsDate = new Date(Date.UTC(date.year, date.month - 1, date.day));
220
+ const dayNum = jsDate.getUTCDay() || 7;
221
+ jsDate.setUTCDate(jsDate.getUTCDate() + 4 - dayNum);
222
+ const yearStart = new Date(Date.UTC(jsDate.getUTCFullYear(), 0, 1));
223
+ return Math.ceil(((jsDate.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
224
+ }
225
+ export function generateHeaderGroups(columns, zoomLevel, locale) {
226
+ if (columns.length === 0) return [];
227
+ const groups = [];
228
+ switch (zoomLevel) {
229
+ case "day": {
230
+ let currentDate = columns[0].date;
231
+ let groupStart = 0;
232
+ for (let i = 0; i < columns.length; i++) {
233
+ if (columns[i].date.compare(currentDate) !== 0) {
234
+ const span2 = i - groupStart;
235
+ groups.push({
236
+ label: formatGroupLabel(currentDate, "day", locale),
237
+ span: span2,
238
+ leftPx: columns[groupStart].leftPx,
239
+ widthPx: span2 * columns[0].widthPx
240
+ });
241
+ currentDate = columns[i].date;
242
+ groupStart = i;
243
+ }
244
+ }
245
+ const span = columns.length - groupStart;
246
+ groups.push({
247
+ label: formatGroupLabel(currentDate, "day", locale),
248
+ span,
249
+ leftPx: columns[groupStart].leftPx,
250
+ widthPx: span * columns[0].widthPx
251
+ });
252
+ break;
253
+ }
254
+ case "week": {
255
+ let currentWeekStart = startOfWeekByDay(columns[0].date, 1);
256
+ let groupStartIdx = 0;
257
+ for (let i = 0; i < columns.length; i++) {
258
+ const ws = startOfWeekByDay(columns[i].date, 1);
259
+ if (ws.compare(currentWeekStart) !== 0) {
260
+ pushGroupFromColumns(groups, columns, groupStartIdx, i, currentWeekStart, "week", locale);
261
+ currentWeekStart = ws;
262
+ groupStartIdx = i;
263
+ }
264
+ }
265
+ pushGroupFromColumns(groups, columns, groupStartIdx, columns.length, currentWeekStart, "week", locale);
266
+ break;
267
+ }
268
+ case "month": {
269
+ let currentKey = `${columns[0].date.year}-${columns[0].date.month}`;
270
+ let groupStartIdx = 0;
271
+ for (let i = 0; i < columns.length; i++) {
272
+ const key = `${columns[i].date.year}-${columns[i].date.month}`;
273
+ if (key !== currentKey) {
274
+ pushGroupFromColumns(groups, columns, groupStartIdx, i, columns[groupStartIdx].date, "month", locale);
275
+ currentKey = key;
276
+ groupStartIdx = i;
277
+ }
278
+ }
279
+ pushGroupFromColumns(groups, columns, groupStartIdx, columns.length, columns[groupStartIdx].date, "month", locale);
280
+ break;
281
+ }
282
+ case "quarter": {
283
+ const firstColMonth = getMonthForWeekColumn(columns[0].date);
284
+ let currentKey = `${firstColMonth.year}-${firstColMonth.month}`;
285
+ let groupStartIdx = 0;
286
+ for (let i = 0; i < columns.length; i++) {
287
+ const colMonth = getMonthForWeekColumn(columns[i].date);
288
+ const key = `${colMonth.year}-${colMonth.month}`;
289
+ if (key !== currentKey) {
290
+ pushGroupFromColumns(groups, columns, groupStartIdx, i, columns[groupStartIdx].date, "quarter", locale);
291
+ currentKey = key;
292
+ groupStartIdx = i;
293
+ }
294
+ }
295
+ pushGroupFromColumns(groups, columns, groupStartIdx, columns.length, columns[groupStartIdx].date, "quarter", locale);
296
+ break;
297
+ }
298
+ case "year": {
299
+ let currentYear = columns[0].date.year;
300
+ let groupStartIdx = 0;
301
+ for (let i = 0; i < columns.length; i++) {
302
+ if (columns[i].date.year !== currentYear) {
303
+ pushGroupFromColumns(groups, columns, groupStartIdx, i, columns[groupStartIdx].date, "year", locale);
304
+ currentYear = columns[i].date.year;
305
+ groupStartIdx = i;
306
+ }
307
+ }
308
+ pushGroupFromColumns(groups, columns, groupStartIdx, columns.length, columns[groupStartIdx].date, "year", locale);
309
+ break;
310
+ }
311
+ }
312
+ return groups;
313
+ }
314
+ function pushGroupFromColumns(groups, columns, startIdx, endIdx, date, zoomLevel, locale) {
315
+ const span = endIdx - startIdx;
316
+ if (span <= 0) return;
317
+ const leftPx = columns[startIdx].leftPx;
318
+ const widthPx = columns[endIdx - 1].leftPx + columns[endIdx - 1].widthPx - leftPx;
319
+ groups.push({
320
+ label: formatGroupLabel(date, zoomLevel, locale),
321
+ span,
322
+ leftPx,
323
+ widthPx
324
+ });
325
+ }
326
+ function getMonthForWeekColumn(weekStartDate) {
327
+ const thursday = weekStartDate.add({ days: 3 });
328
+ return { year: thursday.year, month: thursday.month };
329
+ }
330
+ function formatGroupLabel(date, zoomLevel, locale) {
331
+ const jsDate = new Date(date.year, date.month - 1, date.day);
332
+ switch (zoomLevel) {
333
+ case "day":
334
+ return new Intl.DateTimeFormat(locale, { weekday: "short", month: "short", day: "numeric" }).format(jsDate);
335
+ case "week":
336
+ return `W${getWeekNumber(date)} \u2013 ${new Intl.DateTimeFormat(locale, { month: "short", year: "numeric" }).format(jsDate)}`;
337
+ case "month":
338
+ case "quarter":
339
+ return new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }).format(jsDate);
340
+ case "year":
341
+ return String(date.year);
342
+ }
343
+ }
344
+ export function buildTaskTree(tasks) {
345
+ const taskIds = new Set(tasks.map((t) => t.id));
346
+ const childrenMap = /* @__PURE__ */ new Map();
347
+ for (const task of tasks) {
348
+ const key = task.parentId != null && taskIds.has(task.parentId) ? task.parentId : null;
349
+ if (task.parentId != null && !taskIds.has(task.parentId)) {
350
+ console.warn(`[GanttChart] Task "${task.id}" references unknown parentId "${task.parentId}" \u2014 promoted to root.`);
351
+ }
352
+ const list = childrenMap.get(key);
353
+ if (list) list.push(task);
354
+ else childrenMap.set(key, [task]);
355
+ }
356
+ function build(task, depth) {
357
+ const children = (childrenMap.get(task.id) ?? []).map((child) => build(child, depth + 1));
358
+ return { task: { ...task }, children, depth };
359
+ }
360
+ const roots = childrenMap.get(null) ?? [];
361
+ return roots.map((root) => build(root, 0));
362
+ }
363
+ export function flattenVisibleTasks(tree, expandedIds) {
364
+ const result = [];
365
+ function walk(nodes) {
366
+ for (const node of nodes) {
367
+ const isGroup = node.children.length > 0;
368
+ const isExpanded = isGroup && expandedIds.has(node.task.id);
369
+ result.push({
370
+ task: node.task,
371
+ depth: node.depth,
372
+ isGroup,
373
+ isExpanded,
374
+ childCount: node.children.length
375
+ });
376
+ if (isExpanded) {
377
+ walk(node.children);
378
+ }
379
+ }
380
+ }
381
+ walk(tree);
382
+ return result;
383
+ }
384
+ export function computeGroupSpans(tree) {
385
+ function walk(node) {
386
+ if (node.children.length === 0) {
387
+ return { start: node.task.start, end: node.task.end };
388
+ }
389
+ let minStart = node.task.start;
390
+ let maxEnd = node.task.end;
391
+ for (const child of node.children) {
392
+ const span = walk(child);
393
+ if (span.start.compare(minStart) < 0) minStart = span.start;
394
+ if (span.end.compare(maxEnd) > 0) maxEnd = span.end;
395
+ }
396
+ node.task.start = minStart;
397
+ node.task.end = maxEnd;
398
+ return { start: minStart, end: maxEnd };
399
+ }
400
+ for (const root of tree) {
401
+ walk(root);
402
+ }
403
+ }
404
+ export function getAllGroupIds(tree) {
405
+ const ids = /* @__PURE__ */ new Set();
406
+ function walk(nodes) {
407
+ for (const node of nodes) {
408
+ if (node.children.length > 0) {
409
+ ids.add(node.task.id);
410
+ walk(node.children);
411
+ }
412
+ }
413
+ }
414
+ walk(tree);
415
+ return ids;
416
+ }
417
+ export function computeDependencyPaths(bars, dependencies, rowHeight) {
418
+ const barMap = /* @__PURE__ */ new Map();
419
+ for (const bar of bars) {
420
+ barMap.set(bar.task.id, bar);
421
+ }
422
+ const paths = [];
423
+ for (const dep of dependencies) {
424
+ const from = barMap.get(dep.fromId);
425
+ const to = barMap.get(dep.toId);
426
+ if (!from || !to) continue;
427
+ const result = buildDependencyPath(from, to, rowHeight);
428
+ paths.push({ dependency: dep, path: result.path, from, to, fromPoint: result.fromPoint, toPoint: result.toPoint });
429
+ }
430
+ return paths;
431
+ }
432
+ export function buildDependencyPath(from, to, rowHeight) {
433
+ const stubLength = 20;
434
+ const r = 4;
435
+ const depthIndent = 16;
436
+ const fromOffset = from.isGroup ? 0 : from.depth * depthIndent;
437
+ const toOffset = to.isGroup ? 0 : to.depth * depthIndent;
438
+ const fromX = from.leftPx + fromOffset + from.widthPx;
439
+ const fromY = from.rowIndex * rowHeight + rowHeight / 2;
440
+ const toX = to.leftPx + toOffset;
441
+ const toY = to.rowIndex * rowHeight + rowHeight / 2;
442
+ const fromPoint = { x: fromX, y: fromY };
443
+ const toPoint = { x: toX, y: toY };
444
+ const endX = toX;
445
+ if (fromY === toY) {
446
+ if (endX > fromX + stubLength) {
447
+ return { path: `M ${fromX} ${fromY} L ${endX} ${toY}`, fromPoint, toPoint };
448
+ }
449
+ const midY2 = fromY - rowHeight * 0.6;
450
+ const midX2 = fromX + stubLength;
451
+ const approachX2 = endX - stubLength;
452
+ return {
453
+ path: [
454
+ `M ${fromX} ${fromY}`,
455
+ `L ${midX2 - r} ${fromY}`,
456
+ `Q ${midX2} ${fromY}, ${midX2} ${fromY - r}`,
457
+ `L ${midX2} ${midY2 + r}`,
458
+ `Q ${midX2} ${midY2}, ${midX2 - r} ${midY2}`,
459
+ `L ${approachX2 + r} ${midY2}`,
460
+ `Q ${approachX2} ${midY2}, ${approachX2} ${midY2 + r}`,
461
+ `L ${approachX2} ${toY - r}`,
462
+ `Q ${approachX2} ${toY}, ${approachX2 + r} ${toY}`,
463
+ `L ${endX} ${toY}`
464
+ ].join(" "),
465
+ fromPoint,
466
+ toPoint
467
+ };
468
+ }
469
+ if (endX > fromX + stubLength * 2) {
470
+ const midX2 = fromX + stubLength;
471
+ return {
472
+ path: [
473
+ `M ${fromX} ${fromY}`,
474
+ `L ${midX2 - r} ${fromY}`,
475
+ `Q ${midX2} ${fromY}, ${midX2} ${fromY + (toY > fromY ? r : -r)}`,
476
+ `L ${midX2} ${toY - (toY > fromY ? r : -r)}`,
477
+ `Q ${midX2} ${toY}, ${midX2 + r} ${toY}`,
478
+ `L ${endX} ${toY}`
479
+ ].join(" "),
480
+ fromPoint,
481
+ toPoint
482
+ };
483
+ }
484
+ const midX = fromX + stubLength;
485
+ const approachX = endX - stubLength;
486
+ const goingDown = toY > fromY;
487
+ const midY = goingDown ? Math.min(fromY + rowHeight / 2, toY - rowHeight / 2) : Math.max(fromY - rowHeight / 2, toY + rowHeight / 2);
488
+ return {
489
+ path: [
490
+ `M ${fromX} ${fromY}`,
491
+ `L ${midX - r} ${fromY}`,
492
+ `Q ${midX} ${fromY}, ${midX} ${fromY + (goingDown ? r : -r)}`,
493
+ `L ${midX} ${midY - (goingDown ? r : -r)}`,
494
+ `Q ${midX} ${midY}, ${midX - r} ${midY}`,
495
+ `L ${approachX + r} ${midY}`,
496
+ `Q ${approachX} ${midY}, ${approachX} ${midY + (goingDown ? r : -r)}`,
497
+ `L ${approachX} ${toY - (goingDown ? r : -r)}`,
498
+ `Q ${approachX} ${toY}, ${approachX + r} ${toY}`,
499
+ `L ${endX} ${toY}`
500
+ ].join(" "),
501
+ fromPoint,
502
+ toPoint
503
+ };
504
+ }
505
+ export function computeGanttLayout(tasks, dependencies, expandedIds, zoomLevel, unitWidth, rowHeight, locale) {
506
+ const tree = buildTaskTree(tasks);
507
+ computeGroupSpans(tree);
508
+ const visible = flattenVisibleTasks(tree, expandedIds);
509
+ const bounds = computeTimelineBounds(tasks, zoomLevel);
510
+ const timelineStart = bounds.start;
511
+ const timelineEnd = bounds.end;
512
+ const columns = generateColumns(timelineStart, timelineEnd, zoomLevel, unitWidth, locale);
513
+ const headerGroups = generateHeaderGroups(columns, zoomLevel, locale);
514
+ const totalWidth = columns.length > 0 ? columns[columns.length - 1].leftPx + columns[columns.length - 1].widthPx : 0;
515
+ const taskBars = visible.map((item, index) => {
516
+ const { task, depth, isGroup, isExpanded, childCount } = item;
517
+ const isMilestone = task.milestone;
518
+ const leftPx = dateToPixel(task.start, timelineStart, zoomLevel, unitWidth);
519
+ const endPx = dateToPixel(task.end.add({ days: 1 }), timelineStart, zoomLevel, unitWidth);
520
+ const widthPx = isMilestone ? 0 : Math.max(endPx - leftPx, unitWidth / 2);
521
+ return {
522
+ task,
523
+ rowIndex: index,
524
+ depth,
525
+ isGroup,
526
+ isExpanded,
527
+ childCount,
528
+ leftPx,
529
+ widthPx,
530
+ isMilestone
531
+ };
532
+ });
533
+ const dependencyPaths = computeDependencyPaths(taskBars, dependencies, rowHeight);
534
+ const todayDate = today(getLocalTimeZone());
535
+ let todayOffsetPx = null;
536
+ if (todayDate.compare(timelineStart) >= 0 && todayDate.compare(timelineEnd) <= 0) {
537
+ todayOffsetPx = dateToPixel(todayDate, timelineStart, zoomLevel, unitWidth);
538
+ }
539
+ const totalHeight = visible.length * rowHeight;
540
+ return {
541
+ taskBars,
542
+ dependencyPaths,
543
+ columns,
544
+ headerGroups,
545
+ totalWidth,
546
+ totalHeight,
547
+ todayOffsetPx
548
+ };
549
+ }
@@ -5,7 +5,8 @@ import {
5
5
  add,
6
6
  subtract
7
7
  } from "#std/date";
8
- import { hasTimeComponent, startOfWeekByDay } from "./event-calendar.js";
8
+ import { hasTimeComponent } from "./event-calendar.js";
9
+ import { startOfWeekByDay } from "./date.js";
9
10
  export function getVisibleRange(displayDate, view, weekStartsOn, fixedWeeks) {
10
11
  if (view === "month") {
11
12
  const monthStart = startOf(displayDate, "month");