@primeui/scheduler-core 0.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +1 -0
- package/dist/calendar/index.d.mts +57 -0
- package/dist/calendar/index.mjs +1 -0
- package/dist/chunk-2B3YLWHA.mjs +196 -0
- package/dist/chunk-2THQAZ26.mjs +1669 -0
- package/dist/chunk-5KORIWDT.mjs +41 -0
- package/dist/chunk-5N4ZOBJV.mjs +866 -0
- package/dist/chunk-6OZAPQZ5.mjs +229 -0
- package/dist/chunk-6PK5WSKT.mjs +369 -0
- package/dist/chunk-6VYWVIGM.mjs +1170 -0
- package/dist/chunk-AAVM7UCG.mjs +100 -0
- package/dist/chunk-C7ADJGNV.mjs +157 -0
- package/dist/chunk-DYW6WUHE.mjs +277 -0
- package/dist/chunk-F5W5HD7S.mjs +285 -0
- package/dist/chunk-FIBAZFC4.mjs +871 -0
- package/dist/chunk-HPC5B3AR.mjs +558 -0
- package/dist/chunk-KQGRXTP5.mjs +650 -0
- package/dist/chunk-NMX4BW42.mjs +672 -0
- package/dist/chunk-NX46LPLF.mjs +440 -0
- package/dist/chunk-NZGJN7HG.mjs +314 -0
- package/dist/chunk-QDMZBJDV.mjs +251 -0
- package/dist/chunk-QR2SVYAD.mjs +1144 -0
- package/dist/chunk-SYJ5O4KH.mjs +136 -0
- package/dist/chunk-TNKJPFGI.mjs +569 -0
- package/dist/chunk-UMAMDBU4.mjs +1 -0
- package/dist/chunk-W2SJW3QQ.mjs +3925 -0
- package/dist/chunk-WFUJWDST.mjs +352 -0
- package/dist/chunk-XUBQ2IQS.mjs +1 -0
- package/dist/chunk-ZUKUKGNK.mjs +613 -0
- package/dist/controllers/index.d.mts +384 -0
- package/dist/controllers/index.mjs +13 -0
- package/dist/date-D_CjQPmM.d.mts +74 -0
- package/dist/display-format-CLVvRt4I.d.mts +57 -0
- package/dist/event/index.d.mts +267 -0
- package/dist/event/index.mjs +8 -0
- package/dist/event-surface-_R_LHD95.d.mts +21 -0
- package/dist/event.positioning-BdzAVPk7.d.mts +51 -0
- package/dist/event.utils-QSNdd-3W.d.mts +35 -0
- package/dist/index.d.mts +1128 -0
- package/dist/index.mjs +1022 -0
- package/dist/interaction/index.d.mts +442 -0
- package/dist/interaction/index.mjs +9 -0
- package/dist/month/index.d.mts +104 -0
- package/dist/month/index.mjs +6 -0
- package/dist/overlay-BYM9B6nC.d.mts +64 -0
- package/dist/resource/index.d.mts +172 -0
- package/dist/resource/index.mjs +1 -0
- package/dist/selection-CO_98HdS.d.mts +56 -0
- package/dist/time-grid/index.d.mts +92 -0
- package/dist/time-grid/index.mjs +13 -0
- package/dist/timeline/index.d.mts +165 -0
- package/dist/timeline/index.mjs +6 -0
- package/dist/touch-BhsMWsjf.d.mts +69 -0
- package/dist/utils/index.d.mts +494 -0
- package/dist/utils/index.mjs +17 -0
- package/dist/views/index.d.mts +51 -0
- package/dist/views/index.mjs +8 -0
- package/dist/views/timeline/index.d.mts +37 -0
- package/dist/views/timeline/index.mjs +4 -0
- package/dist/year/index.d.mts +70 -0
- package/dist/year/index.mjs +6 -0
- package/package.json +58 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { getLocalTimezone, convertEventsToTimezone } from './chunk-QDMZBJDV.mjs';
|
|
2
|
+
import { formatGregorianYear, formatGregorianMonthTitle, formatGregorianWeekday, formatGregorianDayNumber, toDateString } from './chunk-WFUJWDST.mjs';
|
|
3
|
+
import { getSchedulerCalendarAdapter } from './chunk-DYW6WUHE.mjs';
|
|
4
|
+
|
|
5
|
+
// src/year/view-model.ts
|
|
6
|
+
function toDate(value) {
|
|
7
|
+
if (!value) return null;
|
|
8
|
+
return value instanceof Date ? value : new Date(value);
|
|
9
|
+
}
|
|
10
|
+
function getFallbackEnd(start, event) {
|
|
11
|
+
if (typeof event.duration === "number" && Number.isFinite(event.duration) && event.duration > 0) {
|
|
12
|
+
return new Date(start.getTime() + event.duration);
|
|
13
|
+
}
|
|
14
|
+
return new Date(start.getTime() + 60 * 60 * 1e3);
|
|
15
|
+
}
|
|
16
|
+
function normalizeEventToDisplayTimezone(event, schedulerTimezone, displayTimezone) {
|
|
17
|
+
const start = toDate(event.start);
|
|
18
|
+
if (!start) {
|
|
19
|
+
return event;
|
|
20
|
+
}
|
|
21
|
+
const end = toDate(event.end) ?? getFallbackEnd(start, event);
|
|
22
|
+
const sourceTimezone = event.timezone ?? schedulerTimezone ?? getLocalTimezone();
|
|
23
|
+
const normalized = {
|
|
24
|
+
...event,
|
|
25
|
+
start,
|
|
26
|
+
end,
|
|
27
|
+
timezone: sourceTimezone
|
|
28
|
+
};
|
|
29
|
+
if (sourceTimezone === displayTimezone) {
|
|
30
|
+
return {
|
|
31
|
+
...normalized,
|
|
32
|
+
timezone: displayTimezone
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const [converted] = convertEventsToTimezone([normalized], displayTimezone);
|
|
36
|
+
return {
|
|
37
|
+
...converted,
|
|
38
|
+
timezone: displayTimezone
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function normalizeEventsToDisplayTimezone(events, schedulerTimezone, resolvedDisplayTimezone) {
|
|
42
|
+
if (!resolvedDisplayTimezone) {
|
|
43
|
+
return events;
|
|
44
|
+
}
|
|
45
|
+
return events.map((event) => normalizeEventToDisplayTimezone(event, schedulerTimezone, resolvedDisplayTimezone));
|
|
46
|
+
}
|
|
47
|
+
function buildWeekdayHeaders(firstDayOfWeek, locale, displayConfig) {
|
|
48
|
+
return Array.from({ length: 7 }, (_, index) => {
|
|
49
|
+
const dayIndex = (firstDayOfWeek + index) % 7;
|
|
50
|
+
return {
|
|
51
|
+
dayIndex,
|
|
52
|
+
label: formatGregorianWeekday(new Date(2024, 0, 7 + dayIndex), locale, "narrow", {}, displayConfig)
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function getRangeInfo(date, selectedRange, calendarAdapter) {
|
|
57
|
+
if (!selectedRange?.start || !selectedRange.end) {
|
|
58
|
+
return { isInRange: false, isRangeStart: false, isRangeEnd: false };
|
|
59
|
+
}
|
|
60
|
+
const day = calendarAdapter.fromDate(date);
|
|
61
|
+
const start = calendarAdapter.fromDate(selectedRange.start);
|
|
62
|
+
const end = calendarAdapter.fromDate(selectedRange.end);
|
|
63
|
+
const min = calendarAdapter.compare(start, end) <= 0 ? start : end;
|
|
64
|
+
const max = calendarAdapter.compare(start, end) <= 0 ? end : start;
|
|
65
|
+
if (calendarAdapter.compare(day, min) < 0 || calendarAdapter.compare(day, max) > 0) {
|
|
66
|
+
return { isInRange: false, isRangeStart: false, isRangeEnd: false };
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
isInRange: true,
|
|
70
|
+
isRangeStart: calendarAdapter.isSameDay(day, start),
|
|
71
|
+
isRangeEnd: calendarAdapter.isSameDay(day, end)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function isDateSelected(date, selectedDates, calendarAdapter) {
|
|
75
|
+
const calendarDate = calendarAdapter.fromDate(date);
|
|
76
|
+
return Boolean(selectedDates?.some((selectedDate) => calendarAdapter.isSameDay(calendarAdapter.fromDate(selectedDate), calendarDate)));
|
|
77
|
+
}
|
|
78
|
+
function getCategoryColor(event, categories, categoryField) {
|
|
79
|
+
const categoryId = event[categoryField];
|
|
80
|
+
const categoryColor = categoryId != null ? categories.find((category) => category.id === categoryId)?.color : void 0;
|
|
81
|
+
return categoryColor ?? event.color;
|
|
82
|
+
}
|
|
83
|
+
function getEventsForDate(date, events, calendarAdapter) {
|
|
84
|
+
const calendarDate = calendarAdapter.fromDate(date);
|
|
85
|
+
const dayStart = calendarAdapter.toDate(calendarDate);
|
|
86
|
+
const dayEnd = calendarAdapter.toDate(calendarAdapter.add(calendarDate, { days: 1 }));
|
|
87
|
+
return events.filter((event) => {
|
|
88
|
+
const start = toDate(event.start);
|
|
89
|
+
if (!start) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const end = toDate(event.end) ?? getFallbackEnd(start, event);
|
|
93
|
+
return start < dayEnd && end > dayStart;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function isDateInCalendarRange(date, minDate, maxDate, calendarAdapter) {
|
|
97
|
+
const calendarDate = calendarAdapter.fromDate(date);
|
|
98
|
+
if (minDate && calendarAdapter.compare(calendarDate, calendarAdapter.fromDate(minDate)) < 0) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (maxDate && calendarAdapter.compare(calendarDate, calendarAdapter.fromDate(maxDate)) > 0) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
function buildDayCell(input) {
|
|
107
|
+
const calendarDate = input.calendarAdapter.fromDate(input.date);
|
|
108
|
+
const todayCalendarDate = input.calendarAdapter.fromDate(input.today);
|
|
109
|
+
const dayOfWeek = input.calendarAdapter.getDayOfWeek(calendarDate);
|
|
110
|
+
const isOtherMonth = calendarDate.month !== input.monthDate.month || calendarDate.year !== input.monthDate.year;
|
|
111
|
+
const rangeInfo = isOtherMonth ? { isInRange: false, isRangeStart: false, isRangeEnd: false } : getRangeInfo(input.date, input.selectedRange, input.calendarAdapter);
|
|
112
|
+
const events = isOtherMonth ? [] : getEventsForDate(input.date, input.events, input.calendarAdapter);
|
|
113
|
+
const eventCount = events.length;
|
|
114
|
+
const indicatorColor = events.map((event) => getCategoryColor(event, input.categories, input.categoryField)).find((color) => Boolean(color));
|
|
115
|
+
const indicator = eventCount > 0 ? { count: eventCount, color: indicatorColor } : null;
|
|
116
|
+
const dayOfMonthLabel = formatGregorianDayNumber(input.date, input.locale, {}, { calendar: input.calendar, numberingSystem: input.numberingSystem });
|
|
117
|
+
return {
|
|
118
|
+
date: input.date,
|
|
119
|
+
dateKey: toDateString(input.date),
|
|
120
|
+
dayOfMonth: calendarDate.day,
|
|
121
|
+
dayOfMonthLabel,
|
|
122
|
+
dayNumberLabel: dayOfMonthLabel,
|
|
123
|
+
isOtherMonth,
|
|
124
|
+
isPlaceholder: isOtherMonth,
|
|
125
|
+
isCurrentMonth: !isOtherMonth,
|
|
126
|
+
isToday: !isOtherMonth && input.calendarAdapter.isSameDay(calendarDate, todayCalendarDate),
|
|
127
|
+
isWeekend: dayOfWeek === 0 || dayOfWeek === 6,
|
|
128
|
+
isDisabled: !isDateInCalendarRange(input.date, input.minDate, input.maxDate, input.calendarAdapter),
|
|
129
|
+
isSelected: !isOtherMonth && isDateSelected(input.date, input.selectedDates, input.calendarAdapter),
|
|
130
|
+
isInRange: rangeInfo.isInRange,
|
|
131
|
+
isRangeStart: rangeInfo.isRangeStart,
|
|
132
|
+
isRangeEnd: rangeInfo.isRangeEnd,
|
|
133
|
+
events,
|
|
134
|
+
eventCount,
|
|
135
|
+
hasEvents: eventCount > 0,
|
|
136
|
+
indicator,
|
|
137
|
+
indicatorColor
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function buildMonthWeeks(input) {
|
|
141
|
+
const monthDate = input.calendarAdapter.startOfMonth({ year: input.year, month: input.month + 1, day: 1 });
|
|
142
|
+
const lastDay = input.calendarAdapter.endOfMonth(monthDate);
|
|
143
|
+
const gridStart = input.calendarAdapter.startOfWeek(monthDate, input.firstDayOfWeek);
|
|
144
|
+
const weeks = [];
|
|
145
|
+
let cursor = gridStart;
|
|
146
|
+
while (input.calendarAdapter.compare(cursor, lastDay) <= 0 || weeks.length === 0 || weeks[weeks.length - 1].days.length < 7) {
|
|
147
|
+
const days = [];
|
|
148
|
+
for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
|
|
149
|
+
const date = input.calendarAdapter.toDate(cursor);
|
|
150
|
+
days.push(
|
|
151
|
+
buildDayCell({
|
|
152
|
+
date,
|
|
153
|
+
monthDate,
|
|
154
|
+
calendarAdapter: input.calendarAdapter,
|
|
155
|
+
today: input.today,
|
|
156
|
+
events: input.events,
|
|
157
|
+
categories: input.categories,
|
|
158
|
+
categoryField: input.categoryField,
|
|
159
|
+
minDate: input.minDate,
|
|
160
|
+
maxDate: input.maxDate,
|
|
161
|
+
selectedDates: input.selectedDates,
|
|
162
|
+
selectedRange: input.selectedRange,
|
|
163
|
+
locale: input.locale,
|
|
164
|
+
calendar: input.calendar,
|
|
165
|
+
numberingSystem: input.numberingSystem
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
cursor = input.calendarAdapter.add(cursor, { days: 1 });
|
|
169
|
+
}
|
|
170
|
+
weeks.push({ days });
|
|
171
|
+
if ((cursor.month !== monthDate.month || cursor.year !== monthDate.year) && weeks.length >= 4) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return weeks;
|
|
176
|
+
}
|
|
177
|
+
function buildYearGridViewModel(input) {
|
|
178
|
+
const calendarAdapter = getSchedulerCalendarAdapter(input.calendar, { locale: input.locale });
|
|
179
|
+
const currentCalendarDate = calendarAdapter.fromDate(input.currentDate);
|
|
180
|
+
const year = currentCalendarDate.year;
|
|
181
|
+
const displayConfig = { calendar: input.calendar, numberingSystem: input.numberingSystem };
|
|
182
|
+
const weekdayHeaders = buildWeekdayHeaders(input.firstDayOfWeek, input.locale, displayConfig);
|
|
183
|
+
const displayEvents = normalizeEventsToDisplayTimezone(input.events, input.schedulerTimezone, input.resolvedDisplayTimezone);
|
|
184
|
+
const categories = input.categories ?? [];
|
|
185
|
+
const categoryField = input.categoryField ?? "categoryId";
|
|
186
|
+
const today = input.now ?? /* @__PURE__ */ new Date();
|
|
187
|
+
const yearStart = calendarAdapter.toDate({ ...calendarAdapter.startOfMonth(currentCalendarDate), month: 1 });
|
|
188
|
+
const monthsInYear = calendarAdapter.getMonthsInYear(currentCalendarDate);
|
|
189
|
+
const yearLabel = formatGregorianYear(yearStart, input.locale, {}, displayConfig);
|
|
190
|
+
const months = Array.from({ length: monthsInYear }, (_, monthIndex) => {
|
|
191
|
+
const date = calendarAdapter.toDate({ year, month: monthIndex + 1, day: 1 });
|
|
192
|
+
const weeks = buildMonthWeeks({
|
|
193
|
+
year,
|
|
194
|
+
month: monthIndex,
|
|
195
|
+
firstDayOfWeek: input.firstDayOfWeek,
|
|
196
|
+
calendarAdapter,
|
|
197
|
+
today,
|
|
198
|
+
events: displayEvents,
|
|
199
|
+
categories,
|
|
200
|
+
categoryField,
|
|
201
|
+
minDate: input.minDate,
|
|
202
|
+
maxDate: input.maxDate,
|
|
203
|
+
selectedDates: input.selectedDates,
|
|
204
|
+
selectedRange: input.selectedRange,
|
|
205
|
+
locale: input.locale,
|
|
206
|
+
calendar: input.calendar,
|
|
207
|
+
numberingSystem: input.numberingSystem
|
|
208
|
+
});
|
|
209
|
+
const days = weeks.flatMap((week) => week.days);
|
|
210
|
+
const monthLabel = formatGregorianMonthTitle(date, input.locale, { year: void 0 }, displayConfig);
|
|
211
|
+
return {
|
|
212
|
+
monthIndex,
|
|
213
|
+
date,
|
|
214
|
+
name: monthLabel,
|
|
215
|
+
monthLabel,
|
|
216
|
+
weekdayHeaders,
|
|
217
|
+
weeks,
|
|
218
|
+
days
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
year,
|
|
223
|
+
yearLabel,
|
|
224
|
+
weekdayHeaders,
|
|
225
|
+
months
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export { buildYearGridViewModel };
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { getLocalTimezone, convertEventsToTimezone } from './chunk-QDMZBJDV.mjs';
|
|
2
|
+
import { startOfDay, isSameDay, addDays, toDateString, formatGregorianMonthTitle, getWeekNumber, formatGregorianDayNumber } from './chunk-WFUJWDST.mjs';
|
|
3
|
+
import { getSchedulerCalendarAdapter } from './chunk-DYW6WUHE.mjs';
|
|
4
|
+
|
|
5
|
+
// src/month/layout.ts
|
|
6
|
+
function daysBetween(a, b) {
|
|
7
|
+
const aMs = new Date(a.getFullYear(), a.getMonth(), a.getDate()).getTime();
|
|
8
|
+
const bMs = new Date(b.getFullYear(), b.getMonth(), b.getDate()).getTime();
|
|
9
|
+
return Math.round((bMs - aMs) / 864e5);
|
|
10
|
+
}
|
|
11
|
+
function durationDays(event) {
|
|
12
|
+
const start = event.start instanceof Date ? event.start : new Date(event.start);
|
|
13
|
+
const end = event.end ? event.end instanceof Date ? event.end : new Date(event.end) : start;
|
|
14
|
+
return Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
15
|
+
}
|
|
16
|
+
function getEventsForMonthWeek(events, days) {
|
|
17
|
+
const weekStart = startOfDay(days[0]);
|
|
18
|
+
const weekEnd = new Date(startOfDay(days[6]));
|
|
19
|
+
weekEnd.setHours(23, 59, 59, 999);
|
|
20
|
+
return events.filter((event) => {
|
|
21
|
+
const eventStart = event.start instanceof Date ? event.start : new Date(event.start);
|
|
22
|
+
const eventEnd = event.end ? event.end instanceof Date ? event.end : new Date(event.end) : eventStart;
|
|
23
|
+
return eventStart <= weekEnd && eventEnd >= weekStart;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function getEventsForMonthDay(events, date) {
|
|
27
|
+
const dayStart = startOfDay(date);
|
|
28
|
+
const dayEnd = new Date(dayStart);
|
|
29
|
+
dayEnd.setHours(23, 59, 59, 999);
|
|
30
|
+
return events.filter((event) => {
|
|
31
|
+
const eventStart = event.start instanceof Date ? event.start : new Date(event.start);
|
|
32
|
+
const eventEnd = event.end ? event.end instanceof Date ? event.end : new Date(event.end) : eventStart;
|
|
33
|
+
return eventStart <= dayEnd && eventEnd >= dayStart;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function getMonthSpanningEventLayout(event, weekStart) {
|
|
37
|
+
const eventStart = event.start instanceof Date ? event.start : new Date(event.start);
|
|
38
|
+
const eventEnd = event.end ? event.end instanceof Date ? event.end : new Date(event.end) : eventStart;
|
|
39
|
+
if (isSameDay(eventStart, eventEnd)) return null;
|
|
40
|
+
const weekEnd = new Date(addDays(weekStart, 6));
|
|
41
|
+
weekEnd.setHours(23, 59, 59, 999);
|
|
42
|
+
let colStart = 1;
|
|
43
|
+
for (let index = 0; index < 7; index++) {
|
|
44
|
+
const day = addDays(weekStart, index);
|
|
45
|
+
if (isSameDay(day, eventStart) || day >= eventStart) {
|
|
46
|
+
colStart = index + 1;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (eventStart < weekStart) {
|
|
51
|
+
colStart = 1;
|
|
52
|
+
}
|
|
53
|
+
let colEnd = colStart + 1;
|
|
54
|
+
for (let index = colStart - 1; index < 7; index++) {
|
|
55
|
+
const day = addDays(weekStart, index);
|
|
56
|
+
if (isSameDay(day, eventEnd) || day >= eventEnd) {
|
|
57
|
+
colEnd = index + 2;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
if (index === 6) {
|
|
61
|
+
colEnd = 8;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (eventEnd > weekEnd) {
|
|
65
|
+
colEnd = 8;
|
|
66
|
+
}
|
|
67
|
+
return { gridColumnStart: colStart, gridColumnEnd: colEnd, row: 0 };
|
|
68
|
+
}
|
|
69
|
+
function buildMonthWeekEventLayout(input) {
|
|
70
|
+
const { events, maxVisibleRows, stackingMode } = input;
|
|
71
|
+
const weekStartDay = startOfDay(input.weekStart);
|
|
72
|
+
const weekEndDay = startOfDay(addDays(weekStartDay, 6));
|
|
73
|
+
const weekDays = Array.from({ length: 7 }, (_, index) => addDays(weekStartDay, index));
|
|
74
|
+
const overlapping = events.filter((event) => {
|
|
75
|
+
const eventStart = event.start instanceof Date ? event.start : new Date(event.start);
|
|
76
|
+
const eventEnd = event.end ? event.end instanceof Date ? event.end : new Date(event.end) : eventStart;
|
|
77
|
+
const eventStartDay = startOfDay(eventStart);
|
|
78
|
+
const eventEndDay = startOfDay(eventEnd);
|
|
79
|
+
return eventStartDay <= weekEndDay && eventEndDay >= weekStartDay;
|
|
80
|
+
});
|
|
81
|
+
const sorted = [...overlapping].sort((a, b) => {
|
|
82
|
+
const da = durationDays(a);
|
|
83
|
+
const db = durationDays(b);
|
|
84
|
+
if (da !== db) return db - da;
|
|
85
|
+
const aStart = a.start instanceof Date ? a.start.getTime() : new Date(a.start).getTime();
|
|
86
|
+
const bStart = b.start instanceof Date ? b.start.getTime() : new Date(b.start).getTime();
|
|
87
|
+
return aStart - bStart;
|
|
88
|
+
});
|
|
89
|
+
const lanes = [];
|
|
90
|
+
const allPositioned = [];
|
|
91
|
+
const rawTimedByDay = {};
|
|
92
|
+
for (const event of sorted) {
|
|
93
|
+
const eventStart = event.start instanceof Date ? event.start : new Date(event.start);
|
|
94
|
+
const eventEnd = event.end ? event.end instanceof Date ? event.end : new Date(event.end) : eventStart;
|
|
95
|
+
const eventStartDay = startOfDay(eventStart);
|
|
96
|
+
const eventEndDay = startOfDay(eventEnd);
|
|
97
|
+
const rawStartCol = daysBetween(weekStartDay, eventStartDay);
|
|
98
|
+
const rawEndCol = daysBetween(weekStartDay, eventEndDay);
|
|
99
|
+
const startCol = Math.max(0, Math.min(6, rawStartCol));
|
|
100
|
+
const endCol = Math.max(0, Math.min(6, rawEndCol));
|
|
101
|
+
if (endCol < startCol) continue;
|
|
102
|
+
const isSpanning = event.allDay === true || eventStartDay.getTime() !== eventEndDay.getTime();
|
|
103
|
+
if (!isSpanning) {
|
|
104
|
+
const key = toDateString(weekDays[startCol]);
|
|
105
|
+
if (!rawTimedByDay[key]) rawTimedByDay[key] = [];
|
|
106
|
+
rawTimedByDay[key].push(event);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const isContinuationStart = rawStartCol < 0;
|
|
110
|
+
const isContinuationEnd = rawEndCol > 6;
|
|
111
|
+
let laneIndex;
|
|
112
|
+
if (stackingMode === "collapse") {
|
|
113
|
+
laneIndex = 0;
|
|
114
|
+
if (lanes.length === 0) lanes.push([]);
|
|
115
|
+
lanes[0].push({ start: startCol, end: endCol });
|
|
116
|
+
} else if (stackingMode === "expand") {
|
|
117
|
+
laneIndex = lanes.length;
|
|
118
|
+
lanes.push([{ start: startCol, end: endCol }]);
|
|
119
|
+
} else {
|
|
120
|
+
laneIndex = -1;
|
|
121
|
+
for (let index = 0; index < lanes.length; index++) {
|
|
122
|
+
const lane = lanes[index];
|
|
123
|
+
const conflict = lane.some((occupant) => !(endCol < occupant.start || startCol > occupant.end));
|
|
124
|
+
if (!conflict) {
|
|
125
|
+
laneIndex = index;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (laneIndex === -1) {
|
|
130
|
+
laneIndex = lanes.length;
|
|
131
|
+
lanes.push([]);
|
|
132
|
+
}
|
|
133
|
+
lanes[laneIndex].push({ start: startCol, end: endCol });
|
|
134
|
+
}
|
|
135
|
+
allPositioned.push({
|
|
136
|
+
event,
|
|
137
|
+
row: laneIndex,
|
|
138
|
+
startCol,
|
|
139
|
+
endCol,
|
|
140
|
+
isContinuationStart,
|
|
141
|
+
isContinuationEnd
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const computeLayout = (budget) => {
|
|
145
|
+
const visibleBars = allPositioned.filter((positioned) => positioned.row < budget);
|
|
146
|
+
const overflowBars = allPositioned.filter((positioned) => positioned.row >= budget);
|
|
147
|
+
const timedByDay = {};
|
|
148
|
+
const overflowByDay = {};
|
|
149
|
+
for (const bar of overflowBars) {
|
|
150
|
+
for (let column = bar.startCol; column <= bar.endCol; column++) {
|
|
151
|
+
const key = toDateString(weekDays[column]);
|
|
152
|
+
if (!overflowByDay[key]) overflowByDay[key] = [];
|
|
153
|
+
overflowByDay[key].push(bar.event);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const dayStats = [];
|
|
157
|
+
for (let column = 0; column < 7; column++) {
|
|
158
|
+
const key = toDateString(weekDays[column]);
|
|
159
|
+
const barsInDay = visibleBars.filter((positioned) => positioned.startCol <= column && column <= positioned.endCol);
|
|
160
|
+
const spanningDepth = barsInDay.length === 0 ? 0 : Math.max(...barsInDay.map((positioned) => positioned.row)) + 1;
|
|
161
|
+
const timedBudget = Math.max(0, budget - spanningDepth);
|
|
162
|
+
const timedList = rawTimedByDay[key] ?? [];
|
|
163
|
+
const visibleTimed = timedList.slice(0, timedBudget);
|
|
164
|
+
const overflowTimed = timedList.slice(timedBudget);
|
|
165
|
+
if (visibleTimed.length > 0) timedByDay[key] = visibleTimed;
|
|
166
|
+
if (overflowTimed.length > 0) {
|
|
167
|
+
if (!overflowByDay[key]) overflowByDay[key] = [];
|
|
168
|
+
overflowByDay[key].push(...overflowTimed);
|
|
169
|
+
}
|
|
170
|
+
dayStats.push({ spanningDepth, overflowCount: (overflowByDay[key] ?? []).length });
|
|
171
|
+
}
|
|
172
|
+
return { positioned: visibleBars, overflowByDay, timedByDay, dayStats };
|
|
173
|
+
};
|
|
174
|
+
const withoutReservation = computeLayout(maxVisibleRows);
|
|
175
|
+
const hasOverflow = withoutReservation.dayStats.some((stats) => stats.overflowCount > 0);
|
|
176
|
+
if (!hasOverflow || maxVisibleRows === 0) return withoutReservation;
|
|
177
|
+
return computeLayout(Math.max(0, maxVisibleRows - 1));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/month/view-model.ts
|
|
181
|
+
function toDate(value) {
|
|
182
|
+
if (!value) return null;
|
|
183
|
+
return value instanceof Date ? value : new Date(value);
|
|
184
|
+
}
|
|
185
|
+
function getFallbackEnd(start, event) {
|
|
186
|
+
if (typeof event.duration === "number" && Number.isFinite(event.duration) && event.duration > 0) {
|
|
187
|
+
return new Date(start.getTime() + event.duration);
|
|
188
|
+
}
|
|
189
|
+
return new Date(start.getTime() + 60 * 60 * 1e3);
|
|
190
|
+
}
|
|
191
|
+
function normalizeEventToDisplayTimezone(event, schedulerTimezone, displayTimezone) {
|
|
192
|
+
const start = toDate(event.start);
|
|
193
|
+
if (!start) {
|
|
194
|
+
return event;
|
|
195
|
+
}
|
|
196
|
+
const end = toDate(event.end) ?? getFallbackEnd(start, event);
|
|
197
|
+
const sourceTimezone = event.timezone ?? schedulerTimezone ?? getLocalTimezone();
|
|
198
|
+
const normalized = {
|
|
199
|
+
...event,
|
|
200
|
+
start,
|
|
201
|
+
end,
|
|
202
|
+
timezone: sourceTimezone
|
|
203
|
+
};
|
|
204
|
+
if (sourceTimezone === displayTimezone) {
|
|
205
|
+
return {
|
|
206
|
+
...normalized,
|
|
207
|
+
timezone: displayTimezone
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const [converted] = convertEventsToTimezone([normalized], displayTimezone);
|
|
211
|
+
return {
|
|
212
|
+
...converted,
|
|
213
|
+
timezone: displayTimezone
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function normalizeEventsToDisplayTimezone(events, schedulerTimezone, resolvedDisplayTimezone) {
|
|
217
|
+
if (!resolvedDisplayTimezone) {
|
|
218
|
+
return events;
|
|
219
|
+
}
|
|
220
|
+
return events.map((event) => normalizeEventToDisplayTimezone(event, schedulerTimezone, resolvedDisplayTimezone));
|
|
221
|
+
}
|
|
222
|
+
function isDateSelected(date, selectedDates, calendarAdapter) {
|
|
223
|
+
const calendarDate = calendarAdapter.fromDate(date);
|
|
224
|
+
return Boolean(selectedDates?.some((selectedDate) => calendarAdapter.isSameDay(calendarAdapter.fromDate(selectedDate), calendarDate)));
|
|
225
|
+
}
|
|
226
|
+
function getRangeInfo(date, selectedRange, calendarAdapter) {
|
|
227
|
+
if (!selectedRange?.start || !selectedRange.end) {
|
|
228
|
+
return { isInRange: false, isRangeStart: false, isRangeEnd: false };
|
|
229
|
+
}
|
|
230
|
+
const day = calendarAdapter.fromDate(date);
|
|
231
|
+
const start = calendarAdapter.fromDate(selectedRange.start);
|
|
232
|
+
const end = calendarAdapter.fromDate(selectedRange.end);
|
|
233
|
+
if (calendarAdapter.compare(day, start) < 0 || calendarAdapter.compare(day, end) > 0) {
|
|
234
|
+
return { isInRange: false, isRangeStart: false, isRangeEnd: false };
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
isInRange: true,
|
|
238
|
+
isRangeStart: calendarAdapter.isSameDay(day, start),
|
|
239
|
+
isRangeEnd: calendarAdapter.isSameDay(day, end)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function isDateInCalendarRange(date, minDate, maxDate, calendarAdapter) {
|
|
243
|
+
const calendarDate = calendarAdapter.fromDate(date);
|
|
244
|
+
if (minDate && calendarAdapter.compare(calendarDate, calendarAdapter.fromDate(minDate)) < 0) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
if (maxDate && calendarAdapter.compare(calendarDate, calendarAdapter.fromDate(maxDate)) > 0) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
function getOwningEventCount(events, date, calendarAdapter) {
|
|
253
|
+
const calendarDate = calendarAdapter.fromDate(date);
|
|
254
|
+
return events.filter((event) => {
|
|
255
|
+
const eventStart = event.start instanceof Date ? event.start : new Date(event.start);
|
|
256
|
+
return calendarAdapter.isSameDay(calendarAdapter.fromDate(eventStart), calendarDate);
|
|
257
|
+
}).length;
|
|
258
|
+
}
|
|
259
|
+
function getVisibleEventsForDay(layout, date, columnIndex) {
|
|
260
|
+
const key = toDateString(date);
|
|
261
|
+
const spanning = layout.positioned.filter((positioned) => positioned.startCol <= columnIndex && columnIndex <= positioned.endCol).map((positioned) => positioned.event);
|
|
262
|
+
return [...spanning, ...layout.timedByDay[key] ?? []];
|
|
263
|
+
}
|
|
264
|
+
function buildMonthGridViewModel(input) {
|
|
265
|
+
const calendarAdapter = getSchedulerCalendarAdapter(input.calendar, { locale: input.locale });
|
|
266
|
+
const currentCalendarDate = calendarAdapter.fromDate(input.currentDate);
|
|
267
|
+
const monthStartCalendarDate = calendarAdapter.startOfMonth(currentCalendarDate);
|
|
268
|
+
const monthEndCalendarDate = calendarAdapter.endOfMonth(currentCalendarDate);
|
|
269
|
+
const monthStart = calendarAdapter.toDate(monthStartCalendarDate);
|
|
270
|
+
const monthEnd = calendarAdapter.toDate(monthEndCalendarDate);
|
|
271
|
+
const displayEvents = normalizeEventsToDisplayTimezone(input.events, input.schedulerTimezone, input.resolvedDisplayTimezone);
|
|
272
|
+
const gridStartCalendarDate = calendarAdapter.startOfWeek(monthStartCalendarDate, input.firstDayOfWeek);
|
|
273
|
+
const today = input.now ?? /* @__PURE__ */ new Date();
|
|
274
|
+
const todayCalendarDate = calendarAdapter.fromDate(today);
|
|
275
|
+
const weekLayouts = /* @__PURE__ */ new Map();
|
|
276
|
+
const dayCells = [];
|
|
277
|
+
const weeks = [];
|
|
278
|
+
const displayConfig = { calendar: input.calendar, numberingSystem: input.numberingSystem };
|
|
279
|
+
const monthTitle = formatGregorianMonthTitle(monthStart, input.locale, {}, displayConfig);
|
|
280
|
+
const getEventsForWeek = (days) => getEventsForMonthWeek(displayEvents, days);
|
|
281
|
+
const getEventsForDay = (date) => getEventsForMonthDay(displayEvents, date);
|
|
282
|
+
for (let weekIndex = 0; weekIndex < 6; weekIndex++) {
|
|
283
|
+
const weekStartCalendarDate = calendarAdapter.add(gridStartCalendarDate, { days: weekIndex * 7 });
|
|
284
|
+
const weekStart = calendarAdapter.toDate(weekStartCalendarDate);
|
|
285
|
+
const dates = Array.from({ length: 7 }, (_, index) => calendarAdapter.toDate(calendarAdapter.add(weekStartCalendarDate, { days: index })));
|
|
286
|
+
const weekEvents = getEventsForWeek(dates);
|
|
287
|
+
const layout = buildMonthWeekEventLayout({
|
|
288
|
+
events: displayEvents,
|
|
289
|
+
weekStart,
|
|
290
|
+
maxVisibleRows: input.maxVisibleRows,
|
|
291
|
+
stackingMode: input.stackingMode
|
|
292
|
+
});
|
|
293
|
+
const weekNumber = getWeekNumber(weekStart, input.weekNumbering, input.locale);
|
|
294
|
+
const days = dates.map((date, columnIndex) => {
|
|
295
|
+
const calendarDate = calendarAdapter.fromDate(date);
|
|
296
|
+
const dayOfWeek = calendarAdapter.getDayOfWeek(calendarDate);
|
|
297
|
+
const dateKey = toDateString(date);
|
|
298
|
+
const events = getEventsForDay(date);
|
|
299
|
+
const rangeInfo = getRangeInfo(date, input.selectedRange, calendarAdapter);
|
|
300
|
+
const dayOfMonthLabel = formatGregorianDayNumber(date, input.locale, {}, displayConfig);
|
|
301
|
+
const dayCell = {
|
|
302
|
+
date,
|
|
303
|
+
dateKey,
|
|
304
|
+
dayOfMonth: calendarDate.day,
|
|
305
|
+
dayOfMonthLabel,
|
|
306
|
+
dayNumberLabel: dayOfMonthLabel,
|
|
307
|
+
isCurrentMonth: calendarDate.month === monthStartCalendarDate.month && calendarDate.year === monthStartCalendarDate.year,
|
|
308
|
+
isToday: calendarAdapter.isSameDay(calendarDate, todayCalendarDate),
|
|
309
|
+
isWeekend: dayOfWeek === 0 || dayOfWeek === 6,
|
|
310
|
+
isDisabled: !isDateInCalendarRange(date, input.minDate, input.maxDate, calendarAdapter),
|
|
311
|
+
isSelected: isDateSelected(date, input.selectedDates, calendarAdapter),
|
|
312
|
+
isInRange: rangeInfo.isInRange,
|
|
313
|
+
isRangeStart: rangeInfo.isRangeStart,
|
|
314
|
+
isRangeEnd: rangeInfo.isRangeEnd,
|
|
315
|
+
events,
|
|
316
|
+
timedEvents: layout.timedByDay[dateKey] ?? [],
|
|
317
|
+
visibleEvents: getVisibleEventsForDay(layout, date, columnIndex),
|
|
318
|
+
overflowEvents: layout.overflowByDay[dateKey] ?? [],
|
|
319
|
+
spanningDepth: layout.dayStats[columnIndex]?.spanningDepth ?? 0,
|
|
320
|
+
overflowCount: layout.dayStats[columnIndex]?.overflowCount ?? 0,
|
|
321
|
+
eventCount: events.length,
|
|
322
|
+
owningEventCount: getOwningEventCount(displayEvents, date, calendarAdapter)
|
|
323
|
+
};
|
|
324
|
+
dayCells.push(dayCell);
|
|
325
|
+
return dayCell;
|
|
326
|
+
});
|
|
327
|
+
weekLayouts.set(toDateString(weekStart), layout);
|
|
328
|
+
weeks.push({
|
|
329
|
+
days,
|
|
330
|
+
weekNumber,
|
|
331
|
+
events: weekEvents,
|
|
332
|
+
layout
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
const getWeekLayout = (weekStart) => {
|
|
336
|
+
const key = toDateString(calendarAdapter.toDate(calendarAdapter.fromDate(weekStart)));
|
|
337
|
+
return weekLayouts.get(key) ?? buildMonthWeekEventLayout({
|
|
338
|
+
events: displayEvents,
|
|
339
|
+
weekStart,
|
|
340
|
+
maxVisibleRows: input.maxVisibleRows,
|
|
341
|
+
stackingMode: input.stackingMode
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
const getOverflowEvents = (date) => {
|
|
345
|
+
const calendarDate = calendarAdapter.fromDate(date);
|
|
346
|
+
return dayCells.find((day) => calendarAdapter.isSameDay(calendarAdapter.fromDate(day.date), calendarDate))?.overflowEvents ?? [];
|
|
347
|
+
};
|
|
348
|
+
return {
|
|
349
|
+
monthStart,
|
|
350
|
+
monthEnd,
|
|
351
|
+
monthTitle,
|
|
352
|
+
events: displayEvents,
|
|
353
|
+
weeks,
|
|
354
|
+
dayCells,
|
|
355
|
+
getEventsForDay,
|
|
356
|
+
getEventsForWeek,
|
|
357
|
+
getEventLayout: getMonthSpanningEventLayout,
|
|
358
|
+
getWeekLayout,
|
|
359
|
+
getOverflowCount: (date, maxRows) => {
|
|
360
|
+
if (maxRows == null) {
|
|
361
|
+
return getOverflowEvents(date).length;
|
|
362
|
+
}
|
|
363
|
+
return Math.max(0, getEventsForDay(date).length - maxRows);
|
|
364
|
+
},
|
|
365
|
+
getOverflowEvents
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export { buildMonthGridViewModel, buildMonthWeekEventLayout, daysBetween, durationDays, getEventsForMonthDay, getEventsForMonthWeek, getMonthSpanningEventLayout };
|