@allhailai/tempusmachina-core 1.0.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.
- package/LICENSE +21 -0
- package/dist/index.cjs +1669 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +848 -0
- package/dist/index.d.ts +848 -0
- package/dist/index.js +1593 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1669 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var temporalPolyfill = require('temporal-polyfill');
|
|
4
|
+
|
|
5
|
+
// src/types/event.ts
|
|
6
|
+
function eventToPlainObject(event) {
|
|
7
|
+
return {
|
|
8
|
+
id: event.id,
|
|
9
|
+
seriesId: event.seriesId ?? null,
|
|
10
|
+
title: event.title,
|
|
11
|
+
description: event.description,
|
|
12
|
+
start: event.start.toString(),
|
|
13
|
+
end: event.end.toString(),
|
|
14
|
+
isAllDay: event.isAllDay,
|
|
15
|
+
duration: event.duration.toString(),
|
|
16
|
+
resourceIds: event.resourceIds,
|
|
17
|
+
color: event.color,
|
|
18
|
+
backgroundColor: event.backgroundColor,
|
|
19
|
+
borderColor: event.borderColor,
|
|
20
|
+
textColor: event.textColor,
|
|
21
|
+
className: event.className,
|
|
22
|
+
display: event.display,
|
|
23
|
+
editable: event.editable,
|
|
24
|
+
startEditable: event.startEditable,
|
|
25
|
+
durationEditable: event.durationEditable,
|
|
26
|
+
resourceEditable: event.resourceEditable,
|
|
27
|
+
overlap: event.overlap,
|
|
28
|
+
extendedProps: event.extendedProps
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/types/view.ts
|
|
33
|
+
function viewTypeFromPreset(preset) {
|
|
34
|
+
if (preset.startsWith("dayGrid")) return "dayGrid";
|
|
35
|
+
if (preset.startsWith("timeGrid")) return "timeGrid";
|
|
36
|
+
if (preset.startsWith("timeline")) return "timeline";
|
|
37
|
+
if (preset.startsWith("multiMonth")) return "multiMonth";
|
|
38
|
+
if (preset.startsWith("resourceTimeGrid")) return "resourceTimeGrid";
|
|
39
|
+
return "agenda";
|
|
40
|
+
}
|
|
41
|
+
function viewUnitFromPreset(preset) {
|
|
42
|
+
if (preset.endsWith("Day")) return "day";
|
|
43
|
+
if (preset.endsWith("Week")) return "week";
|
|
44
|
+
return "month";
|
|
45
|
+
}
|
|
46
|
+
function normalizeEventSourceDef(input, index) {
|
|
47
|
+
if (typeof input === "object" && !Array.isArray(input) && "id" in input && "events" in input) {
|
|
48
|
+
return input;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
id: `source-${index}`,
|
|
52
|
+
events: input
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function rangesOverlap(a, b) {
|
|
56
|
+
return temporalPolyfill.Temporal.PlainDate.compare(a.start, b.end) < 0 && temporalPolyfill.Temporal.PlainDate.compare(b.start, a.end) < 0;
|
|
57
|
+
}
|
|
58
|
+
function rangeContains(range, date) {
|
|
59
|
+
return temporalPolyfill.Temporal.PlainDate.compare(date, range.start) >= 0 && temporalPolyfill.Temporal.PlainDate.compare(date, range.end) < 0;
|
|
60
|
+
}
|
|
61
|
+
function clampDateToRange(date, range) {
|
|
62
|
+
if (temporalPolyfill.Temporal.PlainDate.compare(date, range.start) < 0) return range.start;
|
|
63
|
+
const lastDay = range.end.subtract({ days: 1 });
|
|
64
|
+
if (temporalPolyfill.Temporal.PlainDate.compare(date, lastDay) > 0) return lastDay;
|
|
65
|
+
return date;
|
|
66
|
+
}
|
|
67
|
+
function zonedRangesOverlap(aStart, aEnd, bStart, bEnd) {
|
|
68
|
+
return temporalPolyfill.Temporal.ZonedDateTime.compare(aStart, bEnd) < 0 && temporalPolyfill.Temporal.ZonedDateTime.compare(bStart, aEnd) < 0;
|
|
69
|
+
}
|
|
70
|
+
function zonedRangeContains(start, end, point) {
|
|
71
|
+
return temporalPolyfill.Temporal.ZonedDateTime.compare(point, start) >= 0 && temporalPolyfill.Temporal.ZonedDateTime.compare(point, end) < 0;
|
|
72
|
+
}
|
|
73
|
+
function createDateRange(start, end) {
|
|
74
|
+
return { start, end };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/utils/invariant.ts
|
|
78
|
+
function invariant(condition, message) {
|
|
79
|
+
if (!condition) {
|
|
80
|
+
throw new Error(`[tempus-machina] ${message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/engine/event-store.ts
|
|
85
|
+
var EventStore = class _EventStore {
|
|
86
|
+
events;
|
|
87
|
+
constructor(events) {
|
|
88
|
+
this.events = events;
|
|
89
|
+
}
|
|
90
|
+
static empty() {
|
|
91
|
+
return new _EventStore(/* @__PURE__ */ new Map());
|
|
92
|
+
}
|
|
93
|
+
static from(inputs, timezone, defaults) {
|
|
94
|
+
const events = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const input of inputs) {
|
|
96
|
+
const transformed = defaults?.eventDataTransform ? defaults.eventDataTransform(input) : input;
|
|
97
|
+
const normalized = normalizeEvent(transformed, timezone, defaults);
|
|
98
|
+
events.set(normalized.id, normalized);
|
|
99
|
+
}
|
|
100
|
+
return new _EventStore(events);
|
|
101
|
+
}
|
|
102
|
+
add(input, timezone = "UTC", defaults) {
|
|
103
|
+
const nextEvents = new Map(this.events);
|
|
104
|
+
const transformed = defaults?.eventDataTransform ? defaults.eventDataTransform(input) : input;
|
|
105
|
+
const normalized = normalizeEvent(transformed, timezone, defaults);
|
|
106
|
+
nextEvents.set(normalized.id, normalized);
|
|
107
|
+
return new _EventStore(nextEvents);
|
|
108
|
+
}
|
|
109
|
+
update(id, patch, timezone = "UTC", defaults) {
|
|
110
|
+
const existing = this.events.get(id);
|
|
111
|
+
if (!existing) return this;
|
|
112
|
+
const nextEvents = new Map(this.events);
|
|
113
|
+
const merged = {
|
|
114
|
+
id: existing.id,
|
|
115
|
+
title: patch.title ?? existing.title,
|
|
116
|
+
description: patch.description ?? existing.description,
|
|
117
|
+
start: patch.start ?? existing.start,
|
|
118
|
+
end: patch.end ?? existing.end,
|
|
119
|
+
color: patch.color ?? existing.color ?? void 0,
|
|
120
|
+
backgroundColor: patch.backgroundColor ?? existing.backgroundColor ?? void 0,
|
|
121
|
+
borderColor: patch.borderColor ?? existing.borderColor ?? void 0,
|
|
122
|
+
textColor: patch.textColor ?? existing.textColor ?? void 0,
|
|
123
|
+
className: patch.className ?? existing.className ?? void 0,
|
|
124
|
+
display: patch.display ?? existing.display,
|
|
125
|
+
editable: patch.editable ?? existing.editable,
|
|
126
|
+
startEditable: patch.startEditable ?? existing.startEditable,
|
|
127
|
+
durationEditable: patch.durationEditable ?? existing.durationEditable,
|
|
128
|
+
resourceEditable: patch.resourceEditable ?? existing.resourceEditable,
|
|
129
|
+
overlap: patch.overlap ?? existing.overlap,
|
|
130
|
+
constraint: patch.constraint ?? existing.constraint ?? void 0,
|
|
131
|
+
resourceIds: patch.resourceIds ?? existing.resourceIds,
|
|
132
|
+
extendedProps: patch.extendedProps ?? existing.extendedProps
|
|
133
|
+
};
|
|
134
|
+
const normalized = normalizeEvent(merged, timezone, defaults);
|
|
135
|
+
nextEvents.set(id, normalized);
|
|
136
|
+
return new _EventStore(nextEvents);
|
|
137
|
+
}
|
|
138
|
+
remove(id) {
|
|
139
|
+
const nextEvents = new Map(this.events);
|
|
140
|
+
nextEvents.delete(id);
|
|
141
|
+
return new _EventStore(nextEvents);
|
|
142
|
+
}
|
|
143
|
+
getAll() {
|
|
144
|
+
return Array.from(this.events.values());
|
|
145
|
+
}
|
|
146
|
+
getById(id) {
|
|
147
|
+
return this.events.get(id);
|
|
148
|
+
}
|
|
149
|
+
getInRange(range, timezone = "UTC") {
|
|
150
|
+
const rangeStart = range.start.toZonedDateTime({
|
|
151
|
+
timeZone: timezone,
|
|
152
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
153
|
+
});
|
|
154
|
+
const rangeEnd = range.end.toZonedDateTime({
|
|
155
|
+
timeZone: timezone,
|
|
156
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
157
|
+
});
|
|
158
|
+
return this.getAll().filter(
|
|
159
|
+
(event) => zonedRangesOverlap(event.start, event.end, rangeStart, rangeEnd)
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
getForResource(resourceId) {
|
|
163
|
+
return this.getAll().filter((event) => event.resourceIds.includes(resourceId));
|
|
164
|
+
}
|
|
165
|
+
get size() {
|
|
166
|
+
return this.events.size;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
function normalizeEvent(input, timezone, defaults) {
|
|
170
|
+
invariant(input.id, "Event must have an id");
|
|
171
|
+
invariant(input.title, "Event must have a title");
|
|
172
|
+
const globalEditable = defaults?.editable ?? true;
|
|
173
|
+
let start;
|
|
174
|
+
let end;
|
|
175
|
+
let isAllDay = false;
|
|
176
|
+
if (input.date) {
|
|
177
|
+
isAllDay = true;
|
|
178
|
+
const defaultDuration = defaults?.defaultAllDayEventDuration ?? temporalPolyfill.Temporal.Duration.from({ days: 1 });
|
|
179
|
+
const endDate = input.endDate ?? input.date.add(defaultDuration);
|
|
180
|
+
start = input.date.toZonedDateTime({
|
|
181
|
+
timeZone: timezone,
|
|
182
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
183
|
+
});
|
|
184
|
+
end = endDate.toZonedDateTime({
|
|
185
|
+
timeZone: timezone,
|
|
186
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
187
|
+
});
|
|
188
|
+
} else if (input.start) {
|
|
189
|
+
start = toZonedDateTime(input.start, timezone);
|
|
190
|
+
if (input.end) {
|
|
191
|
+
end = toZonedDateTime(input.end, timezone);
|
|
192
|
+
} else if (input.duration) {
|
|
193
|
+
end = start.add(input.duration);
|
|
194
|
+
} else {
|
|
195
|
+
const defaultDuration = defaults?.defaultTimedEventDuration ?? temporalPolyfill.Temporal.Duration.from({ hours: 1 });
|
|
196
|
+
end = start.add(defaultDuration);
|
|
197
|
+
}
|
|
198
|
+
} else if (defaults?.defaultAllDay) {
|
|
199
|
+
isAllDay = true;
|
|
200
|
+
const today = temporalPolyfill.Temporal.Now.plainDateISO();
|
|
201
|
+
start = today.toZonedDateTime({
|
|
202
|
+
timeZone: timezone,
|
|
203
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
204
|
+
});
|
|
205
|
+
end = today.add({ days: 1 }).toZonedDateTime({ timeZone: timezone, plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00") });
|
|
206
|
+
} else {
|
|
207
|
+
throw new Error(`[tempus-machina] Event "${input.id}" must have either 'date' or 'start'`);
|
|
208
|
+
}
|
|
209
|
+
const duration = start.until(end);
|
|
210
|
+
const resolvedColor = input.color ?? defaults?.eventColor ?? null;
|
|
211
|
+
const resolvedBgColor = input.backgroundColor ?? defaults?.eventBackgroundColor ?? null;
|
|
212
|
+
const resolvedBorderColor = input.borderColor ?? defaults?.eventBorderColor ?? null;
|
|
213
|
+
const resolvedTextColor = input.textColor ?? defaults?.eventTextColor ?? null;
|
|
214
|
+
return {
|
|
215
|
+
id: input.id,
|
|
216
|
+
seriesId: input.recurrenceId,
|
|
217
|
+
title: input.title,
|
|
218
|
+
description: input.description ?? "",
|
|
219
|
+
start,
|
|
220
|
+
end,
|
|
221
|
+
isAllDay,
|
|
222
|
+
duration,
|
|
223
|
+
resourceIds: input.resourceIds ?? (input.resourceId ? [input.resourceId] : []),
|
|
224
|
+
color: resolvedColor,
|
|
225
|
+
backgroundColor: resolvedBgColor,
|
|
226
|
+
borderColor: resolvedBorderColor,
|
|
227
|
+
textColor: resolvedTextColor,
|
|
228
|
+
className: input.className ?? null,
|
|
229
|
+
display: input.display ?? "auto",
|
|
230
|
+
editable: input.editable ?? globalEditable,
|
|
231
|
+
startEditable: input.startEditable ?? input.editable ?? globalEditable,
|
|
232
|
+
durationEditable: input.durationEditable ?? input.editable ?? globalEditable,
|
|
233
|
+
resourceEditable: input.resourceEditable ?? input.editable ?? globalEditable,
|
|
234
|
+
overlap: input.overlap ?? true,
|
|
235
|
+
constraint: input.constraint ?? null,
|
|
236
|
+
allow: input.allow ?? null,
|
|
237
|
+
extendedProps: input.extendedProps ?? {}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function toZonedDateTime(dt, timezone) {
|
|
241
|
+
if ("timeZoneId" in dt && typeof dt.timeZoneId === "string") {
|
|
242
|
+
return dt;
|
|
243
|
+
}
|
|
244
|
+
return dt.toZonedDateTime(timezone);
|
|
245
|
+
}
|
|
246
|
+
function getWeekStart(date, firstDayOfWeek = 0) {
|
|
247
|
+
const dayOfWeek0 = date.dayOfWeek % 7;
|
|
248
|
+
const diff = (dayOfWeek0 - firstDayOfWeek + 7) % 7;
|
|
249
|
+
return date.subtract({ days: diff });
|
|
250
|
+
}
|
|
251
|
+
function getWeekEnd(date, firstDayOfWeek = 0) {
|
|
252
|
+
return getWeekStart(date, firstDayOfWeek).add({ days: 7 });
|
|
253
|
+
}
|
|
254
|
+
function* iterateDays(start, end) {
|
|
255
|
+
let current = start;
|
|
256
|
+
while (temporalPolyfill.Temporal.PlainDate.compare(current, end) < 0) {
|
|
257
|
+
yield current;
|
|
258
|
+
current = current.add({ days: 1 });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function durationBetween(start, end) {
|
|
262
|
+
return start.until(end);
|
|
263
|
+
}
|
|
264
|
+
function daysBetween(start, end) {
|
|
265
|
+
return start.until(end, { largestUnit: "day" }).days;
|
|
266
|
+
}
|
|
267
|
+
function isWeekend(date) {
|
|
268
|
+
return date.dayOfWeek >= 6;
|
|
269
|
+
}
|
|
270
|
+
function getMonthStart(date) {
|
|
271
|
+
return date.with({ day: 1 });
|
|
272
|
+
}
|
|
273
|
+
function getMonthEnd(date) {
|
|
274
|
+
return date.with({ day: 1 }).add({ months: 1 });
|
|
275
|
+
}
|
|
276
|
+
function getDaysInMonth(date) {
|
|
277
|
+
return date.daysInMonth;
|
|
278
|
+
}
|
|
279
|
+
function toISODayOfWeek(dayOfWeek0) {
|
|
280
|
+
return dayOfWeek0 === 0 ? 7 : dayOfWeek0;
|
|
281
|
+
}
|
|
282
|
+
function fromISODayOfWeek(isoDayOfWeek) {
|
|
283
|
+
return isoDayOfWeek % 7;
|
|
284
|
+
}
|
|
285
|
+
function addDurationToDateTime(dateTime, duration) {
|
|
286
|
+
return dateTime.add(duration);
|
|
287
|
+
}
|
|
288
|
+
function getToday() {
|
|
289
|
+
return temporalPolyfill.Temporal.Now.plainDateISO();
|
|
290
|
+
}
|
|
291
|
+
function compareDates(a, b) {
|
|
292
|
+
return temporalPolyfill.Temporal.PlainDate.compare(a, b);
|
|
293
|
+
}
|
|
294
|
+
function compareZonedDateTimes(a, b) {
|
|
295
|
+
return temporalPolyfill.Temporal.ZonedDateTime.compare(a, b);
|
|
296
|
+
}
|
|
297
|
+
function compareDateTimes(a, b) {
|
|
298
|
+
return temporalPolyfill.Temporal.PlainDateTime.compare(a, b);
|
|
299
|
+
}
|
|
300
|
+
function compareTimes(a, b) {
|
|
301
|
+
return temporalPolyfill.Temporal.PlainTime.compare(a, b);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/engine/date-spine.ts
|
|
305
|
+
function generateDateSpine(config) {
|
|
306
|
+
const { type } = config;
|
|
307
|
+
switch (type) {
|
|
308
|
+
case "dayGrid":
|
|
309
|
+
return generateDayGridSpine(config);
|
|
310
|
+
case "timeGrid":
|
|
311
|
+
return generateTimeGridSpine(config);
|
|
312
|
+
case "timeline":
|
|
313
|
+
return generateTimelineSpine(config);
|
|
314
|
+
case "agenda":
|
|
315
|
+
return generateAgendaSpine(config);
|
|
316
|
+
default:
|
|
317
|
+
return generateDayGridSpine(config);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function generateDayGridSpine(config) {
|
|
321
|
+
const { visibleRange, firstDayOfWeek, hiddenDays } = config;
|
|
322
|
+
const gridStart = getWeekStart(visibleRange.start, firstDayOfWeek);
|
|
323
|
+
const gridEnd = getGridEnd(visibleRange.end, firstDayOfWeek);
|
|
324
|
+
const dates = [];
|
|
325
|
+
const weeks = [];
|
|
326
|
+
let currentWeek = [];
|
|
327
|
+
for (const date of iterateDays(gridStart, gridEnd)) {
|
|
328
|
+
const dayOfWeek0 = date.dayOfWeek % 7;
|
|
329
|
+
if (hiddenDays.includes(dayOfWeek0)) continue;
|
|
330
|
+
dates.push(date);
|
|
331
|
+
currentWeek.push(date);
|
|
332
|
+
if (currentWeek.length === 7 - hiddenDays.length) {
|
|
333
|
+
weeks.push(currentWeek);
|
|
334
|
+
currentWeek = [];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (currentWeek.length > 0) {
|
|
338
|
+
weeks.push(currentWeek);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
dates,
|
|
342
|
+
weeks,
|
|
343
|
+
totalDays: dates.length,
|
|
344
|
+
totalSlots: 0
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function generateTimeGridSpine(config) {
|
|
348
|
+
const { visibleRange, hiddenDays } = config;
|
|
349
|
+
const dates = [];
|
|
350
|
+
const slots = [];
|
|
351
|
+
for (const date of iterateDays(visibleRange.start, visibleRange.end)) {
|
|
352
|
+
const dayOfWeek0 = date.dayOfWeek % 7;
|
|
353
|
+
if (hiddenDays.includes(dayOfWeek0)) continue;
|
|
354
|
+
dates.push(date);
|
|
355
|
+
slots.push(generateSlotsForDay(date, config));
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
dates,
|
|
359
|
+
slots,
|
|
360
|
+
totalDays: dates.length,
|
|
361
|
+
totalSlots: slots.reduce((sum, s) => sum + s.length, 0)
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function generateTimelineSpine(config) {
|
|
365
|
+
return generateTimeGridSpine(config);
|
|
366
|
+
}
|
|
367
|
+
function generateAgendaSpine(config) {
|
|
368
|
+
const { visibleRange, hiddenDays } = config;
|
|
369
|
+
const dates = [];
|
|
370
|
+
for (const date of iterateDays(visibleRange.start, visibleRange.end)) {
|
|
371
|
+
const dayOfWeek0 = date.dayOfWeek % 7;
|
|
372
|
+
if (hiddenDays.includes(dayOfWeek0)) continue;
|
|
373
|
+
dates.push(date);
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
dates,
|
|
377
|
+
totalDays: dates.length,
|
|
378
|
+
totalSlots: 0
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function generateSlotsForDay(date, config) {
|
|
382
|
+
const { slotDuration, slotMinTime, slotMaxTime, businessHours } = config;
|
|
383
|
+
const slots = [];
|
|
384
|
+
let currentTime = slotMinTime;
|
|
385
|
+
let index = 0;
|
|
386
|
+
while (compareTimes(currentTime, slotMaxTime) < 0) {
|
|
387
|
+
const slotStart = date.toPlainDateTime(currentTime);
|
|
388
|
+
const nextTime = addTimeByDuration(currentTime, slotDuration);
|
|
389
|
+
const effectiveEndTime = compareTimes(nextTime, slotMaxTime) <= 0 ? nextTime : slotMaxTime;
|
|
390
|
+
const slotEnd = date.toPlainDateTime(effectiveEndTime);
|
|
391
|
+
const dayOfWeek0 = date.dayOfWeek % 7;
|
|
392
|
+
slots.push({
|
|
393
|
+
start: slotStart,
|
|
394
|
+
end: slotEnd,
|
|
395
|
+
duration: slotDuration,
|
|
396
|
+
index,
|
|
397
|
+
isBusinessHour: businessHours ? businessHours.daysOfWeek.includes(dayOfWeek0) && compareTimes(currentTime, businessHours.startTime) >= 0 && compareTimes(currentTime, businessHours.endTime) < 0 : false,
|
|
398
|
+
isNow: false,
|
|
399
|
+
// Computed at render time by React layer
|
|
400
|
+
date,
|
|
401
|
+
time: currentTime
|
|
402
|
+
});
|
|
403
|
+
currentTime = effectiveEndTime;
|
|
404
|
+
index++;
|
|
405
|
+
}
|
|
406
|
+
return slots;
|
|
407
|
+
}
|
|
408
|
+
function addTimeByDuration(time, duration) {
|
|
409
|
+
const totalSeconds = time.hour * 3600 + time.minute * 60 + time.second + (duration.hours || 0) * 3600 + (duration.minutes || 0) * 60 + (duration.seconds || 0);
|
|
410
|
+
const clampedSeconds = Math.min(totalSeconds, 24 * 3600 - 1);
|
|
411
|
+
const hours = Math.floor(clampedSeconds / 3600);
|
|
412
|
+
const minutes = Math.floor(clampedSeconds % 3600 / 60);
|
|
413
|
+
const seconds = clampedSeconds % 60;
|
|
414
|
+
return temporalPolyfill.Temporal.PlainTime.from({ hour: hours, minute: minutes, second: seconds });
|
|
415
|
+
}
|
|
416
|
+
function getGridEnd(end, firstDayOfWeek) {
|
|
417
|
+
const weekStart = getWeekStart(end, firstDayOfWeek);
|
|
418
|
+
if (end.equals(weekStart)) return end;
|
|
419
|
+
return weekStart.add({ days: 7 });
|
|
420
|
+
}
|
|
421
|
+
function getMonthVisibleRange(date, firstDayOfWeek) {
|
|
422
|
+
const monthStart = getMonthStart(date);
|
|
423
|
+
const monthEnd = getMonthEnd(date);
|
|
424
|
+
const gridStart = getWeekStart(monthStart, firstDayOfWeek);
|
|
425
|
+
const gridEnd = getGridEnd(monthEnd, firstDayOfWeek);
|
|
426
|
+
return { start: gridStart, end: gridEnd };
|
|
427
|
+
}
|
|
428
|
+
function applyEventOrder(events, eventOrder) {
|
|
429
|
+
const sorted = [...events];
|
|
430
|
+
if (eventOrder) {
|
|
431
|
+
sorted.sort(
|
|
432
|
+
(a, b) => eventOrder(
|
|
433
|
+
{ start: a.start, end: a.end, title: a.title, allDay: a.isAllDay },
|
|
434
|
+
{ start: b.start, end: b.end, title: b.title, allDay: b.isAllDay }
|
|
435
|
+
)
|
|
436
|
+
);
|
|
437
|
+
} else {
|
|
438
|
+
sorted.sort((a, b) => {
|
|
439
|
+
const startCmp = compareZonedDateTimes(a.start, b.start);
|
|
440
|
+
if (startCmp !== 0) return startCmp;
|
|
441
|
+
return compareZonedDateTimes(a.end, b.end) * -1;
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return sorted;
|
|
445
|
+
}
|
|
446
|
+
function partitionByDisplay(events) {
|
|
447
|
+
const foreground = [];
|
|
448
|
+
const background = [];
|
|
449
|
+
for (const event of events) {
|
|
450
|
+
if (event.display === "none") continue;
|
|
451
|
+
if (event.display === "background") {
|
|
452
|
+
background.push(event);
|
|
453
|
+
} else {
|
|
454
|
+
foreground.push(event);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return [foreground, background];
|
|
458
|
+
}
|
|
459
|
+
function solveLayout(events, config) {
|
|
460
|
+
const [foreground] = partitionByDisplay(events);
|
|
461
|
+
if (foreground.length === 0) return [];
|
|
462
|
+
const sorted = applyEventOrder(foreground, config.eventOrder);
|
|
463
|
+
const clusters = findOverlapClusters(sorted);
|
|
464
|
+
const positioned = [];
|
|
465
|
+
for (const cluster of clusters) {
|
|
466
|
+
const columnAssignments = packColumns(cluster);
|
|
467
|
+
const totalColumns = Math.max(...columnAssignments.values()) + 1;
|
|
468
|
+
for (const event of cluster) {
|
|
469
|
+
const column = columnAssignments.get(event.id) ?? 0;
|
|
470
|
+
const { top, height } = computeTimePosition(event, config);
|
|
471
|
+
positioned.push({
|
|
472
|
+
event,
|
|
473
|
+
column,
|
|
474
|
+
totalColumns,
|
|
475
|
+
top,
|
|
476
|
+
height,
|
|
477
|
+
left: column / totalColumns * 100,
|
|
478
|
+
width: 1 / totalColumns * 100,
|
|
479
|
+
daySpan: 1,
|
|
480
|
+
startSlotIndex: 0,
|
|
481
|
+
endSlotIndex: 0,
|
|
482
|
+
isDragging: false,
|
|
483
|
+
isResizing: false,
|
|
484
|
+
isSelected: false,
|
|
485
|
+
isFocused: false
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return positioned;
|
|
490
|
+
}
|
|
491
|
+
function solveLayoutFull(events, config) {
|
|
492
|
+
const [foreground, background] = partitionByDisplay(events);
|
|
493
|
+
const positioned = foreground.length > 0 ? solveLayout(foreground, config) : [];
|
|
494
|
+
const backgroundEvents = background.map((event) => {
|
|
495
|
+
const { top, height } = computeTimePosition(event, config);
|
|
496
|
+
return { event, top, height };
|
|
497
|
+
});
|
|
498
|
+
return { positioned, backgroundEvents };
|
|
499
|
+
}
|
|
500
|
+
function solveDayGridLayout(events, weeks, maxEventsPerCell = 3, timezone = "UTC", eventOrder) {
|
|
501
|
+
const today = temporalPolyfill.Temporal.Now.plainDateISO();
|
|
502
|
+
const [foreground] = partitionByDisplay(events);
|
|
503
|
+
const allDates = weeks.flat();
|
|
504
|
+
const midDate = allDates[Math.floor(allDates.length / 2)];
|
|
505
|
+
const referenceMonth = midDate?.month ?? today.month;
|
|
506
|
+
return weeks.map(
|
|
507
|
+
(week) => week.map((date) => {
|
|
508
|
+
const dayOfWeek0 = date.dayOfWeek % 7;
|
|
509
|
+
const dateStart = date.toZonedDateTime({
|
|
510
|
+
timeZone: timezone,
|
|
511
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
512
|
+
});
|
|
513
|
+
const dateEnd = date.add({ days: 1 }).toZonedDateTime({
|
|
514
|
+
timeZone: timezone,
|
|
515
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
516
|
+
});
|
|
517
|
+
const dayEvents = foreground.filter((event) => {
|
|
518
|
+
return compareZonedDateTimes(event.start, dateEnd) < 0 && compareZonedDateTimes(event.end, dateStart) > 0;
|
|
519
|
+
});
|
|
520
|
+
if (eventOrder) {
|
|
521
|
+
dayEvents.sort(
|
|
522
|
+
(a, b) => eventOrder(
|
|
523
|
+
{ start: a.start, end: a.end, title: a.title, allDay: a.isAllDay },
|
|
524
|
+
{ start: b.start, end: b.end, title: b.title, allDay: b.isAllDay }
|
|
525
|
+
)
|
|
526
|
+
);
|
|
527
|
+
} else {
|
|
528
|
+
dayEvents.sort((a, b) => {
|
|
529
|
+
if (a.isAllDay && !b.isAllDay) return -1;
|
|
530
|
+
if (!a.isAllDay && b.isAllDay) return 1;
|
|
531
|
+
return compareZonedDateTimes(a.start, b.start);
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const visibleEvents = dayEvents.slice(0, maxEventsPerCell);
|
|
535
|
+
const moreCount = Math.max(0, dayEvents.length - maxEventsPerCell);
|
|
536
|
+
const positionedEvents = visibleEvents.map((event, idx) => ({
|
|
537
|
+
event,
|
|
538
|
+
column: 0,
|
|
539
|
+
totalColumns: 1,
|
|
540
|
+
top: 0,
|
|
541
|
+
height: 0,
|
|
542
|
+
left: 0,
|
|
543
|
+
width: 100,
|
|
544
|
+
daySpan: event.isAllDay ? Math.max(1, daysBetween(date, temporalPolyfill.Temporal.PlainDate.from(event.end.toPlainDate()))) : 1,
|
|
545
|
+
startSlotIndex: idx,
|
|
546
|
+
endSlotIndex: idx,
|
|
547
|
+
isDragging: false,
|
|
548
|
+
isResizing: false,
|
|
549
|
+
isSelected: false,
|
|
550
|
+
isFocused: false
|
|
551
|
+
}));
|
|
552
|
+
return {
|
|
553
|
+
date,
|
|
554
|
+
dayOfWeek: dayOfWeek0,
|
|
555
|
+
isToday: date.equals(today),
|
|
556
|
+
isCurrentMonth: date.month === referenceMonth,
|
|
557
|
+
isWeekend: isWeekend(date),
|
|
558
|
+
isHidden: false,
|
|
559
|
+
events: positionedEvents,
|
|
560
|
+
moreCount
|
|
561
|
+
};
|
|
562
|
+
})
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
function findOverlapClusters(sortedEvents) {
|
|
566
|
+
const clusters = [];
|
|
567
|
+
let currentCluster = [];
|
|
568
|
+
let clusterEnd = null;
|
|
569
|
+
for (const event of sortedEvents) {
|
|
570
|
+
if (clusterEnd === null || compareZonedDateTimes(event.start, clusterEnd) >= 0) {
|
|
571
|
+
if (currentCluster.length > 0) {
|
|
572
|
+
clusters.push(currentCluster);
|
|
573
|
+
}
|
|
574
|
+
currentCluster = [event];
|
|
575
|
+
clusterEnd = event.end;
|
|
576
|
+
} else {
|
|
577
|
+
currentCluster.push(event);
|
|
578
|
+
if (compareZonedDateTimes(event.end, clusterEnd) > 0) {
|
|
579
|
+
clusterEnd = event.end;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (currentCluster.length > 0) {
|
|
584
|
+
clusters.push(currentCluster);
|
|
585
|
+
}
|
|
586
|
+
return clusters;
|
|
587
|
+
}
|
|
588
|
+
function packColumns(cluster) {
|
|
589
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
590
|
+
const columnEnds = [];
|
|
591
|
+
for (const event of cluster) {
|
|
592
|
+
let assignedColumn = -1;
|
|
593
|
+
for (let col = 0; col < columnEnds.length; col++) {
|
|
594
|
+
if (compareZonedDateTimes(event.start, columnEnds[col]) >= 0) {
|
|
595
|
+
assignedColumn = col;
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (assignedColumn === -1) {
|
|
600
|
+
assignedColumn = columnEnds.length;
|
|
601
|
+
columnEnds.push(event.end);
|
|
602
|
+
} else {
|
|
603
|
+
columnEnds[assignedColumn] = event.end;
|
|
604
|
+
}
|
|
605
|
+
assignments.set(event.id, assignedColumn);
|
|
606
|
+
}
|
|
607
|
+
return assignments;
|
|
608
|
+
}
|
|
609
|
+
function computeTimePosition(event, config) {
|
|
610
|
+
const minTime = config.slotMinTime;
|
|
611
|
+
const maxTime = config.slotMaxTime;
|
|
612
|
+
const totalMinutes = timeToMinutes(maxTime) - timeToMinutes(minTime);
|
|
613
|
+
if (totalMinutes <= 0) return { top: 0, height: 0 };
|
|
614
|
+
const eventStartMinutes = Math.max(
|
|
615
|
+
timeToMinutes(event.start.toPlainTime()),
|
|
616
|
+
timeToMinutes(minTime)
|
|
617
|
+
);
|
|
618
|
+
const eventEndMinutes = Math.min(timeToMinutes(event.end.toPlainTime()), timeToMinutes(maxTime));
|
|
619
|
+
const top = (eventStartMinutes - timeToMinutes(minTime)) / totalMinutes * 100;
|
|
620
|
+
const height = (eventEndMinutes - eventStartMinutes) / totalMinutes * 100;
|
|
621
|
+
return { top: Math.max(0, top), height: Math.max(0, height) };
|
|
622
|
+
}
|
|
623
|
+
function timeToMinutes(time) {
|
|
624
|
+
return time.hour * 60 + time.minute;
|
|
625
|
+
}
|
|
626
|
+
function clampToValidRange(date, validRange) {
|
|
627
|
+
if (!validRange) return date;
|
|
628
|
+
if (temporalPolyfill.Temporal.PlainDate.compare(date, validRange.start) < 0) return validRange.start;
|
|
629
|
+
if (temporalPolyfill.Temporal.PlainDate.compare(date, validRange.end) >= 0) {
|
|
630
|
+
return validRange.end.subtract({ days: 1 });
|
|
631
|
+
}
|
|
632
|
+
return date;
|
|
633
|
+
}
|
|
634
|
+
function navigateNext(date, preset, validRange) {
|
|
635
|
+
const unit = viewUnitFromPreset(preset);
|
|
636
|
+
let next;
|
|
637
|
+
switch (unit) {
|
|
638
|
+
case "day":
|
|
639
|
+
next = date.add({ days: 1 });
|
|
640
|
+
break;
|
|
641
|
+
case "week":
|
|
642
|
+
next = date.add({ weeks: 1 });
|
|
643
|
+
break;
|
|
644
|
+
case "month":
|
|
645
|
+
next = date.add({ months: 1 });
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
return clampToValidRange(next, validRange);
|
|
649
|
+
}
|
|
650
|
+
function navigatePrev(date, preset, validRange) {
|
|
651
|
+
const unit = viewUnitFromPreset(preset);
|
|
652
|
+
let prev;
|
|
653
|
+
switch (unit) {
|
|
654
|
+
case "day":
|
|
655
|
+
prev = date.subtract({ days: 1 });
|
|
656
|
+
break;
|
|
657
|
+
case "week":
|
|
658
|
+
prev = date.subtract({ weeks: 1 });
|
|
659
|
+
break;
|
|
660
|
+
case "month":
|
|
661
|
+
prev = date.subtract({ months: 1 });
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
return clampToValidRange(prev, validRange);
|
|
665
|
+
}
|
|
666
|
+
function getVisibleRange(date, preset, firstDayOfWeek = 0) {
|
|
667
|
+
const unit = viewUnitFromPreset(preset);
|
|
668
|
+
switch (unit) {
|
|
669
|
+
case "day":
|
|
670
|
+
return { start: date, end: date.add({ days: 1 }) };
|
|
671
|
+
case "week": {
|
|
672
|
+
const weekStart = getWeekStart(date, firstDayOfWeek);
|
|
673
|
+
return { start: weekStart, end: weekStart.add({ days: 7 }) };
|
|
674
|
+
}
|
|
675
|
+
case "month": {
|
|
676
|
+
const monthStart = getMonthStart(date);
|
|
677
|
+
const monthEnd = getMonthEnd(date);
|
|
678
|
+
const gridStart = getWeekStart(monthStart, firstDayOfWeek);
|
|
679
|
+
let gridEnd = getWeekStart(monthEnd, firstDayOfWeek);
|
|
680
|
+
if (!monthEnd.equals(gridEnd)) {
|
|
681
|
+
gridEnd = gridEnd.add({ days: 7 });
|
|
682
|
+
}
|
|
683
|
+
return { start: gridStart, end: gridEnd };
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function getToday2() {
|
|
688
|
+
return temporalPolyfill.Temporal.Now.plainDateISO();
|
|
689
|
+
}
|
|
690
|
+
function generateTimeSlots(date, config) {
|
|
691
|
+
const { slotDuration, slotMinTime, slotMaxTime, businessHours } = config;
|
|
692
|
+
const slots = [];
|
|
693
|
+
let currentTime = slotMinTime;
|
|
694
|
+
let index = 0;
|
|
695
|
+
while (compareTimes(currentTime, slotMaxTime) < 0) {
|
|
696
|
+
const nextTime = addTimeByDuration2(currentTime, slotDuration);
|
|
697
|
+
const effectiveEnd = compareTimes(nextTime, slotMaxTime) <= 0 ? nextTime : slotMaxTime;
|
|
698
|
+
const dayOfWeek0 = date.dayOfWeek % 7;
|
|
699
|
+
slots.push({
|
|
700
|
+
start: date.toPlainDateTime(currentTime),
|
|
701
|
+
end: date.toPlainDateTime(effectiveEnd),
|
|
702
|
+
duration: slotDuration,
|
|
703
|
+
index,
|
|
704
|
+
isBusinessHour: isBusinessHour(currentTime, dayOfWeek0, businessHours),
|
|
705
|
+
isNow: false,
|
|
706
|
+
date,
|
|
707
|
+
time: currentTime
|
|
708
|
+
});
|
|
709
|
+
currentTime = effectiveEnd;
|
|
710
|
+
index++;
|
|
711
|
+
}
|
|
712
|
+
return slots;
|
|
713
|
+
}
|
|
714
|
+
function snapToSlot(dateTime, slotDuration) {
|
|
715
|
+
const totalMinutes = dateTime.hour * 60 + dateTime.minute;
|
|
716
|
+
const slotMinutes = (slotDuration.hours || 0) * 60 + (slotDuration.minutes || 0) || 30;
|
|
717
|
+
const snappedMinutes = Math.round(totalMinutes / slotMinutes) * slotMinutes;
|
|
718
|
+
const hours = Math.floor(snappedMinutes / 60);
|
|
719
|
+
const minutes = snappedMinutes % 60;
|
|
720
|
+
return dateTime.with({
|
|
721
|
+
hour: Math.min(hours, 23),
|
|
722
|
+
minute: minutes,
|
|
723
|
+
second: 0,
|
|
724
|
+
millisecond: 0,
|
|
725
|
+
microsecond: 0,
|
|
726
|
+
nanosecond: 0
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
function getSlotIndex(time, config) {
|
|
730
|
+
const slotMinutes = (config.slotDuration.hours || 0) * 60 + (config.slotDuration.minutes || 0) || 30;
|
|
731
|
+
const startMinutes = config.slotMinTime.hour * 60 + config.slotMinTime.minute;
|
|
732
|
+
const timeMinutes = time.hour * 60 + time.minute;
|
|
733
|
+
return Math.floor((timeMinutes - startMinutes) / slotMinutes);
|
|
734
|
+
}
|
|
735
|
+
function getNowPosition(config) {
|
|
736
|
+
const now = temporalPolyfill.Temporal.Now.plainTimeISO();
|
|
737
|
+
const minMinutes = config.slotMinTime.hour * 60 + config.slotMinTime.minute;
|
|
738
|
+
const maxMinutes = config.slotMaxTime.hour * 60 + config.slotMaxTime.minute;
|
|
739
|
+
const nowMinutes = now.hour * 60 + now.minute;
|
|
740
|
+
if (nowMinutes < minMinutes || nowMinutes > maxMinutes) return -1;
|
|
741
|
+
return (nowMinutes - minMinutes) / (maxMinutes - minMinutes) * 100;
|
|
742
|
+
}
|
|
743
|
+
function isBusinessHour(time, dayOfWeek0, businessHours) {
|
|
744
|
+
if (!businessHours) return false;
|
|
745
|
+
if (!businessHours.daysOfWeek.includes(dayOfWeek0)) return false;
|
|
746
|
+
return compareTimes(time, businessHours.startTime) >= 0 && compareTimes(time, businessHours.endTime) < 0;
|
|
747
|
+
}
|
|
748
|
+
function addTimeByDuration2(time, duration) {
|
|
749
|
+
const totalSeconds = time.hour * 3600 + time.minute * 60 + time.second + (duration.hours || 0) * 3600 + (duration.minutes || 0) * 60 + (duration.seconds || 0);
|
|
750
|
+
const clampedSeconds = Math.min(totalSeconds, 24 * 3600 - 1);
|
|
751
|
+
const hours = Math.floor(clampedSeconds / 3600);
|
|
752
|
+
const minutes = Math.floor(clampedSeconds % 3600 / 60);
|
|
753
|
+
const seconds = clampedSeconds % 60;
|
|
754
|
+
return temporalPolyfill.Temporal.PlainTime.from({ hour: hours, minute: minutes, second: seconds });
|
|
755
|
+
}
|
|
756
|
+
function validateEventDrop(event, newStart, newEnd, allEvents, config, viewConfig, resourceId) {
|
|
757
|
+
if (event.allow) {
|
|
758
|
+
const allowed = event.allow({ start: newStart, end: newEnd, resourceId });
|
|
759
|
+
if (!allowed) return false;
|
|
760
|
+
}
|
|
761
|
+
if (config.eventAllow) {
|
|
762
|
+
const allowed = config.eventAllow({ start: newStart, end: newEnd, resourceId }, event);
|
|
763
|
+
if (!allowed) return false;
|
|
764
|
+
}
|
|
765
|
+
if (event.constraint) {
|
|
766
|
+
if (typeof event.constraint === "string") {
|
|
767
|
+
if (!isWithinBusinessHours(newStart, newEnd, viewConfig)) return false;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (config.eventConstraint) {
|
|
771
|
+
if (config.eventConstraint === "businessHours") {
|
|
772
|
+
if (!isWithinBusinessHours(newStart, newEnd, viewConfig)) return false;
|
|
773
|
+
} else {
|
|
774
|
+
if (!isWithinDateRange(newStart, newEnd, config.eventConstraint)) return false;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (!checkOverlap(event, newStart, newEnd, allEvents, config)) return false;
|
|
778
|
+
if (viewConfig.validRange) {
|
|
779
|
+
if (!isWithinValidRange(newStart.toPlainDate(), viewConfig.validRange)) return false;
|
|
780
|
+
if (!isWithinValidRange(newEnd.toPlainDate(), viewConfig.validRange)) return false;
|
|
781
|
+
}
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
function validateSelection(start, end, allEvents, config, viewConfig, resourceId) {
|
|
785
|
+
if (config.selectAllow) {
|
|
786
|
+
if (!config.selectAllow({ start, end, resourceId })) return false;
|
|
787
|
+
}
|
|
788
|
+
if (config.selectConstraint) {
|
|
789
|
+
if (config.selectConstraint === "businessHours") {
|
|
790
|
+
if (!isWithinBusinessHours(start, end, viewConfig)) return false;
|
|
791
|
+
} else {
|
|
792
|
+
if (!isWithinDateRange(start, end, config.selectConstraint)) return false;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (config.selectOverlap !== void 0) {
|
|
796
|
+
for (const existing of allEvents) {
|
|
797
|
+
if (zonedRangesOverlap(start, end, existing.start, existing.end)) {
|
|
798
|
+
if (typeof config.selectOverlap === "function") {
|
|
799
|
+
if (!config.selectOverlap(existing)) return false;
|
|
800
|
+
} else if (!config.selectOverlap) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return true;
|
|
807
|
+
}
|
|
808
|
+
function isWithinValidRange(date, validRange) {
|
|
809
|
+
return temporalPolyfill.Temporal.PlainDate.compare(date, validRange.start) >= 0 && temporalPolyfill.Temporal.PlainDate.compare(date, validRange.end) < 0;
|
|
810
|
+
}
|
|
811
|
+
function isWithinBusinessHours(start, end, viewConfig) {
|
|
812
|
+
if (!viewConfig.businessHours) return true;
|
|
813
|
+
const bh = viewConfig.businessHours;
|
|
814
|
+
const startDay = start.toPlainDate().dayOfWeek % 7;
|
|
815
|
+
const startTime = start.toPlainTime();
|
|
816
|
+
const endTime = end.toPlainTime();
|
|
817
|
+
if (!bh.daysOfWeek.includes(startDay)) return false;
|
|
818
|
+
if (temporalPolyfill.Temporal.PlainTime.compare(startTime, bh.startTime) < 0) return false;
|
|
819
|
+
if (temporalPolyfill.Temporal.PlainTime.compare(endTime, bh.endTime) > 0) return false;
|
|
820
|
+
return true;
|
|
821
|
+
}
|
|
822
|
+
function isWithinDateRange(start, end, range) {
|
|
823
|
+
const startDate = start.toPlainDate();
|
|
824
|
+
const endDate = end.toPlainDate();
|
|
825
|
+
return temporalPolyfill.Temporal.PlainDate.compare(startDate, range.start) >= 0 && temporalPolyfill.Temporal.PlainDate.compare(endDate, range.end) <= 0;
|
|
826
|
+
}
|
|
827
|
+
function checkOverlap(movingEvent, newStart, newEnd, allEvents, config) {
|
|
828
|
+
if (!movingEvent.overlap) {
|
|
829
|
+
for (const existing of allEvents) {
|
|
830
|
+
if (existing.id === movingEvent.id) continue;
|
|
831
|
+
if (zonedRangesOverlap(newStart, newEnd, existing.start, existing.end)) {
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (config.eventOverlap !== void 0) {
|
|
837
|
+
for (const existing of allEvents) {
|
|
838
|
+
if (existing.id === movingEvent.id) continue;
|
|
839
|
+
if (!existing.overlap) continue;
|
|
840
|
+
if (zonedRangesOverlap(newStart, newEnd, existing.start, existing.end)) {
|
|
841
|
+
if (typeof config.eventOverlap === "function") {
|
|
842
|
+
if (!config.eventOverlap(existing, movingEvent)) return false;
|
|
843
|
+
} else if (!config.eventOverlap) {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return true;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// src/engine/event-source-engine.ts
|
|
853
|
+
async function fetchEventSource(source, range, timezone) {
|
|
854
|
+
const { events: input } = source;
|
|
855
|
+
if (Array.isArray(input)) {
|
|
856
|
+
return applySourceDefaults(input, source);
|
|
857
|
+
}
|
|
858
|
+
if (typeof input === "string") {
|
|
859
|
+
return fetchJsonFeed(input, range, timezone, source);
|
|
860
|
+
}
|
|
861
|
+
if (typeof input === "function") {
|
|
862
|
+
const fetchInfo = {
|
|
863
|
+
start: range.start,
|
|
864
|
+
end: range.end,
|
|
865
|
+
startStr: range.start.toString(),
|
|
866
|
+
endStr: range.end.toString(),
|
|
867
|
+
timezone
|
|
868
|
+
};
|
|
869
|
+
const events = await input(fetchInfo);
|
|
870
|
+
return applySourceDefaults(events, source);
|
|
871
|
+
}
|
|
872
|
+
return [];
|
|
873
|
+
}
|
|
874
|
+
async function fetchJsonFeed(url, range, timezone, source) {
|
|
875
|
+
const params = new URLSearchParams({
|
|
876
|
+
start: range.start.toString(),
|
|
877
|
+
end: range.end.toString(),
|
|
878
|
+
timeZone: timezone,
|
|
879
|
+
...source.extraParams
|
|
880
|
+
});
|
|
881
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
882
|
+
const fullUrl = `${url}${separator}${params.toString()}`;
|
|
883
|
+
const response = await fetch(fullUrl);
|
|
884
|
+
if (!response.ok) {
|
|
885
|
+
throw new Error(`Event source fetch failed: ${response.status} ${response.statusText}`);
|
|
886
|
+
}
|
|
887
|
+
const events = await response.json();
|
|
888
|
+
return applySourceDefaults(events, source);
|
|
889
|
+
}
|
|
890
|
+
function applySourceDefaults(events, source) {
|
|
891
|
+
if (!source.color && !source.className && source.editable === void 0 && source.overlap === void 0) {
|
|
892
|
+
return events;
|
|
893
|
+
}
|
|
894
|
+
return events.map((event) => ({
|
|
895
|
+
...event,
|
|
896
|
+
color: event.color ?? source.color,
|
|
897
|
+
className: event.className ?? source.className,
|
|
898
|
+
editable: event.editable ?? source.editable,
|
|
899
|
+
overlap: event.overlap ?? source.overlap
|
|
900
|
+
}));
|
|
901
|
+
}
|
|
902
|
+
async function mergeEventSources(sources, range, timezone) {
|
|
903
|
+
const results = await Promise.allSettled(
|
|
904
|
+
sources.map((source) => fetchEventSource(source, range, timezone))
|
|
905
|
+
);
|
|
906
|
+
const events = [];
|
|
907
|
+
const errors = /* @__PURE__ */ new Map();
|
|
908
|
+
results.forEach((result, index) => {
|
|
909
|
+
if (result.status === "fulfilled") {
|
|
910
|
+
events.push(...result.value);
|
|
911
|
+
} else {
|
|
912
|
+
errors.set(sources[index].id, result.reason);
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
return { events, errors };
|
|
916
|
+
}
|
|
917
|
+
function createEventSources(inputs) {
|
|
918
|
+
return inputs.map((input, index) => {
|
|
919
|
+
const def = normalizeEventSourceDef(input, index);
|
|
920
|
+
return {
|
|
921
|
+
id: def.id,
|
|
922
|
+
def,
|
|
923
|
+
state: "idle",
|
|
924
|
+
events: [],
|
|
925
|
+
error: null,
|
|
926
|
+
lastFetched: null
|
|
927
|
+
};
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
function formatDate(date, locale = "en-US", options) {
|
|
931
|
+
const defaultOptions = {
|
|
932
|
+
year: "numeric",
|
|
933
|
+
month: "long",
|
|
934
|
+
day: "numeric",
|
|
935
|
+
...options
|
|
936
|
+
};
|
|
937
|
+
const jsDate = new Date(date.toString());
|
|
938
|
+
return new Intl.DateTimeFormat(locale, defaultOptions).format(jsDate);
|
|
939
|
+
}
|
|
940
|
+
function formatTime(time, locale = "en-US", options) {
|
|
941
|
+
const defaultOptions = {
|
|
942
|
+
hour: "numeric",
|
|
943
|
+
minute: "2-digit",
|
|
944
|
+
...options
|
|
945
|
+
};
|
|
946
|
+
const jsDate = /* @__PURE__ */ new Date(`2000-01-01T${time.toString()}`);
|
|
947
|
+
return new Intl.DateTimeFormat(locale, defaultOptions).format(jsDate);
|
|
948
|
+
}
|
|
949
|
+
function formatDateRange(start, end, locale = "en-US", separator = " \u2013 ") {
|
|
950
|
+
const startDate = new Date(start.toString());
|
|
951
|
+
const endDate = new Date(end.subtract({ days: 1 }).toString());
|
|
952
|
+
if (start.equals(end.subtract({ days: 1 }))) {
|
|
953
|
+
return new Intl.DateTimeFormat(locale, {
|
|
954
|
+
year: "numeric",
|
|
955
|
+
month: "long",
|
|
956
|
+
day: "numeric"
|
|
957
|
+
}).format(startDate);
|
|
958
|
+
}
|
|
959
|
+
if (start.month === end.subtract({ days: 1 }).month && start.year === end.subtract({ days: 1 }).year) {
|
|
960
|
+
return `${new Intl.DateTimeFormat(locale, { month: "short" }).format(startDate)} ${start.day}${separator}${end.subtract({ days: 1 }).day}, ${start.year}`;
|
|
961
|
+
}
|
|
962
|
+
const startFormatted = new Intl.DateTimeFormat(locale, { month: "short", day: "numeric" }).format(
|
|
963
|
+
startDate
|
|
964
|
+
);
|
|
965
|
+
const endFormatted = new Intl.DateTimeFormat(locale, {
|
|
966
|
+
month: "short",
|
|
967
|
+
day: "numeric",
|
|
968
|
+
year: "numeric"
|
|
969
|
+
}).format(endDate);
|
|
970
|
+
return `${startFormatted}${separator}${endFormatted}`;
|
|
971
|
+
}
|
|
972
|
+
function getViewTitle(date, preset, locale = "en-US", titleFormat, titleRangeSeparator = " \u2013 ") {
|
|
973
|
+
const jsDate = new Date(date.toString());
|
|
974
|
+
if (titleFormat) {
|
|
975
|
+
return new Intl.DateTimeFormat(locale, titleFormat).format(jsDate);
|
|
976
|
+
}
|
|
977
|
+
switch (preset) {
|
|
978
|
+
case "dayGridMonth":
|
|
979
|
+
case "timelineMonth":
|
|
980
|
+
case "agendaMonth":
|
|
981
|
+
return new Intl.DateTimeFormat(locale, { year: "numeric", month: "long" }).format(jsDate);
|
|
982
|
+
case "dayGridWeek":
|
|
983
|
+
case "timeGridWeek":
|
|
984
|
+
case "timelineWeek":
|
|
985
|
+
case "agendaWeek": {
|
|
986
|
+
const weekEnd = date.add({ days: 6 });
|
|
987
|
+
return formatDateRange(date, weekEnd.add({ days: 1 }), locale, titleRangeSeparator);
|
|
988
|
+
}
|
|
989
|
+
case "dayGridDay":
|
|
990
|
+
case "timeGridDay":
|
|
991
|
+
case "timelineDay":
|
|
992
|
+
case "agendaDay":
|
|
993
|
+
return new Intl.DateTimeFormat(locale, {
|
|
994
|
+
weekday: "long",
|
|
995
|
+
year: "numeric",
|
|
996
|
+
month: "long",
|
|
997
|
+
day: "numeric"
|
|
998
|
+
}).format(jsDate);
|
|
999
|
+
default:
|
|
1000
|
+
return new Intl.DateTimeFormat(locale, { year: "numeric", month: "long" }).format(jsDate);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function formatEventTime(dateTime, locale = "en-US", options) {
|
|
1004
|
+
const jsDate = new Date(
|
|
1005
|
+
dateTime.year,
|
|
1006
|
+
dateTime.month - 1,
|
|
1007
|
+
// JS months are 0-indexed
|
|
1008
|
+
dateTime.day,
|
|
1009
|
+
dateTime.hour,
|
|
1010
|
+
dateTime.minute,
|
|
1011
|
+
dateTime.second
|
|
1012
|
+
);
|
|
1013
|
+
return new Intl.DateTimeFormat(locale, {
|
|
1014
|
+
hour: "numeric",
|
|
1015
|
+
minute: "2-digit",
|
|
1016
|
+
...options
|
|
1017
|
+
}).format(jsDate);
|
|
1018
|
+
}
|
|
1019
|
+
function formatSlotLabel(time, locale = "en-US", format) {
|
|
1020
|
+
const defaultFormat = {
|
|
1021
|
+
hour: "numeric",
|
|
1022
|
+
minute: "2-digit",
|
|
1023
|
+
...format
|
|
1024
|
+
};
|
|
1025
|
+
const jsDate = /* @__PURE__ */ new Date(`2000-01-01T${time.toString()}`);
|
|
1026
|
+
return new Intl.DateTimeFormat(locale, defaultFormat).format(jsDate);
|
|
1027
|
+
}
|
|
1028
|
+
function formatDayHeader(date, locale = "en-US", format) {
|
|
1029
|
+
const defaultFormat = {
|
|
1030
|
+
weekday: "short",
|
|
1031
|
+
...format
|
|
1032
|
+
};
|
|
1033
|
+
const jsDate = new Date(date.toString());
|
|
1034
|
+
return new Intl.DateTimeFormat(locale, defaultFormat).format(jsDate);
|
|
1035
|
+
}
|
|
1036
|
+
function getWeekNumber(date) {
|
|
1037
|
+
const jan4 = temporalPolyfill.Temporal.PlainDate.from({ year: date.year, month: 1, day: 4 });
|
|
1038
|
+
const jan4DayOfWeek = jan4.dayOfWeek;
|
|
1039
|
+
const isoWeekStart = jan4.subtract({ days: jan4DayOfWeek - 1 });
|
|
1040
|
+
const daysSinceWeekStart = date.since(isoWeekStart, { largestUnit: "days" }).days;
|
|
1041
|
+
if (daysSinceWeekStart < 0) {
|
|
1042
|
+
return getWeekNumber(temporalPolyfill.Temporal.PlainDate.from({ year: date.year - 1, month: 12, day: 31 }));
|
|
1043
|
+
}
|
|
1044
|
+
const weekNumber = Math.floor(daysSinceWeekStart / 7) + 1;
|
|
1045
|
+
if (weekNumber > 52) {
|
|
1046
|
+
const dec31 = temporalPolyfill.Temporal.PlainDate.from({ year: date.year, month: 12, day: 31 });
|
|
1047
|
+
const dec31Day = dec31.dayOfWeek;
|
|
1048
|
+
if (dec31Day < 4) return 1;
|
|
1049
|
+
}
|
|
1050
|
+
return weekNumber;
|
|
1051
|
+
}
|
|
1052
|
+
var RTL_LOCALES = /* @__PURE__ */ new Set([
|
|
1053
|
+
"ar",
|
|
1054
|
+
"arc",
|
|
1055
|
+
"dv",
|
|
1056
|
+
"fa",
|
|
1057
|
+
"ha",
|
|
1058
|
+
"he",
|
|
1059
|
+
"khw",
|
|
1060
|
+
"ks",
|
|
1061
|
+
"ku",
|
|
1062
|
+
"ps",
|
|
1063
|
+
"ur",
|
|
1064
|
+
"yi"
|
|
1065
|
+
]);
|
|
1066
|
+
function isRtl(locale) {
|
|
1067
|
+
const primary = locale.split("-")[0].toLowerCase();
|
|
1068
|
+
return RTL_LOCALES.has(primary);
|
|
1069
|
+
}
|
|
1070
|
+
var DAY_MAP = {
|
|
1071
|
+
MO: 1,
|
|
1072
|
+
TU: 2,
|
|
1073
|
+
WE: 3,
|
|
1074
|
+
TH: 4,
|
|
1075
|
+
FR: 5,
|
|
1076
|
+
SA: 6,
|
|
1077
|
+
SU: 7
|
|
1078
|
+
};
|
|
1079
|
+
function parseRRule(rruleStr) {
|
|
1080
|
+
const cleanStr = rruleStr.replace(/^RRULE:/i, "");
|
|
1081
|
+
const parts = cleanStr.split(";");
|
|
1082
|
+
const result = {
|
|
1083
|
+
freq: "WEEKLY",
|
|
1084
|
+
interval: 1
|
|
1085
|
+
};
|
|
1086
|
+
for (const part of parts) {
|
|
1087
|
+
const [key, value] = part.split("=");
|
|
1088
|
+
if (!key || !value) continue;
|
|
1089
|
+
switch (key.toUpperCase()) {
|
|
1090
|
+
case "FREQ":
|
|
1091
|
+
result.freq = value.toUpperCase();
|
|
1092
|
+
break;
|
|
1093
|
+
case "INTERVAL":
|
|
1094
|
+
result.interval = parseInt(value, 10);
|
|
1095
|
+
break;
|
|
1096
|
+
case "COUNT":
|
|
1097
|
+
result.count = parseInt(value, 10);
|
|
1098
|
+
break;
|
|
1099
|
+
case "UNTIL":
|
|
1100
|
+
result.until = parseRRuleDate(value);
|
|
1101
|
+
break;
|
|
1102
|
+
case "BYDAY":
|
|
1103
|
+
result.byDay = value.split(",").map((d) => d.trim().toUpperCase());
|
|
1104
|
+
break;
|
|
1105
|
+
case "BYMONTHDAY":
|
|
1106
|
+
result.byMonthDay = value.split(",").map((d) => parseInt(d, 10));
|
|
1107
|
+
break;
|
|
1108
|
+
case "BYMONTH":
|
|
1109
|
+
result.byMonth = value.split(",").map((m) => parseInt(m, 10));
|
|
1110
|
+
break;
|
|
1111
|
+
case "WKST":
|
|
1112
|
+
result.wkst = value.toUpperCase();
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return result;
|
|
1117
|
+
}
|
|
1118
|
+
function parseRRuleDate(dateStr) {
|
|
1119
|
+
const cleanDate = dateStr.replace(/[TZ]/g, "").substring(0, 8);
|
|
1120
|
+
const year = parseInt(cleanDate.substring(0, 4), 10);
|
|
1121
|
+
const month = parseInt(cleanDate.substring(4, 6), 10);
|
|
1122
|
+
const day = parseInt(cleanDate.substring(6, 8), 10);
|
|
1123
|
+
return temporalPolyfill.Temporal.PlainDate.from({ year, month, day });
|
|
1124
|
+
}
|
|
1125
|
+
function expandRRule(start, rruleStr, rangeStart, rangeEnd, exDates = [], maxOccurrences = 365) {
|
|
1126
|
+
const rule = parseRRule(rruleStr);
|
|
1127
|
+
const occurrences = [];
|
|
1128
|
+
const exDateSet = new Set(exDates.map((d) => d.toString()));
|
|
1129
|
+
let count = 0;
|
|
1130
|
+
let current = start;
|
|
1131
|
+
const safetyLimit = maxOccurrences * 10;
|
|
1132
|
+
let iterations = 0;
|
|
1133
|
+
while (iterations < safetyLimit) {
|
|
1134
|
+
iterations++;
|
|
1135
|
+
if (rule.count !== void 0 && count >= rule.count) break;
|
|
1136
|
+
if (rule.until && temporalPolyfill.Temporal.PlainDate.compare(current, rule.until) > 0) break;
|
|
1137
|
+
if (temporalPolyfill.Temporal.PlainDate.compare(current, rangeEnd) >= 0) break;
|
|
1138
|
+
if (matchesRule(current, rule)) {
|
|
1139
|
+
if (temporalPolyfill.Temporal.PlainDate.compare(current, rangeStart) >= 0 && !exDateSet.has(current.toString())) {
|
|
1140
|
+
occurrences.push(current);
|
|
1141
|
+
}
|
|
1142
|
+
count++;
|
|
1143
|
+
if (occurrences.length >= maxOccurrences) break;
|
|
1144
|
+
}
|
|
1145
|
+
current = advanceDate(current, rule);
|
|
1146
|
+
}
|
|
1147
|
+
return occurrences;
|
|
1148
|
+
}
|
|
1149
|
+
function matchesRule(date, rule, _seriesStart) {
|
|
1150
|
+
if (rule.byDay && rule.byDay.length > 0) {
|
|
1151
|
+
const dayOfWeek = date.dayOfWeek;
|
|
1152
|
+
const dayMatch = rule.byDay.some((day) => DAY_MAP[day] === dayOfWeek);
|
|
1153
|
+
if (!dayMatch) return false;
|
|
1154
|
+
}
|
|
1155
|
+
if (rule.byMonthDay && rule.byMonthDay.length > 0) {
|
|
1156
|
+
if (!rule.byMonthDay.includes(date.day)) return false;
|
|
1157
|
+
}
|
|
1158
|
+
if (rule.byMonth && rule.byMonth.length > 0) {
|
|
1159
|
+
if (!rule.byMonth.includes(date.month)) return false;
|
|
1160
|
+
}
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
function advanceDate(current, rule) {
|
|
1164
|
+
switch (rule.freq) {
|
|
1165
|
+
case "DAILY":
|
|
1166
|
+
return current.add({ days: rule.interval });
|
|
1167
|
+
case "WEEKLY":
|
|
1168
|
+
if (rule.byDay && rule.byDay.length > 0) {
|
|
1169
|
+
return current.add({ days: 1 });
|
|
1170
|
+
}
|
|
1171
|
+
return current.add({ weeks: rule.interval });
|
|
1172
|
+
case "MONTHLY":
|
|
1173
|
+
if (rule.byMonthDay && rule.byMonthDay.length > 0) {
|
|
1174
|
+
return current.add({ days: 1 });
|
|
1175
|
+
}
|
|
1176
|
+
return current.add({ months: rule.interval });
|
|
1177
|
+
case "YEARLY":
|
|
1178
|
+
if (rule.byMonth && rule.byMonth.length > 0) {
|
|
1179
|
+
return current.add({ days: 1 });
|
|
1180
|
+
}
|
|
1181
|
+
return current.add({ years: rule.interval });
|
|
1182
|
+
default:
|
|
1183
|
+
return current.add({ days: 1 });
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// src/plugins/daygrid.ts
|
|
1188
|
+
var dayGridLayoutSolver = (events, config, context) => {
|
|
1189
|
+
const spine = generateDateSpine(config);
|
|
1190
|
+
const weeks = spine.weeks ?? [];
|
|
1191
|
+
const dayCells = solveDayGridLayout(events, weeks, config.eventMaxStack ?? 3, "UTC");
|
|
1192
|
+
return {
|
|
1193
|
+
viewType: "dayGrid",
|
|
1194
|
+
dateRange: config.visibleRange,
|
|
1195
|
+
weeks: dayCells
|
|
1196
|
+
};
|
|
1197
|
+
};
|
|
1198
|
+
var dayGridPlugin = {
|
|
1199
|
+
name: "dayGrid",
|
|
1200
|
+
viewTypes: ["dayGrid"],
|
|
1201
|
+
layoutSolvers: {
|
|
1202
|
+
dayGrid: dayGridLayoutSolver
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
// src/plugins/timegrid.ts
|
|
1207
|
+
var timeGridLayoutSolver = (events, config, _context) => {
|
|
1208
|
+
const spine = generateDateSpine(config);
|
|
1209
|
+
const dates = spine.dates;
|
|
1210
|
+
const slotsPerDay = spine.slots ?? [];
|
|
1211
|
+
const allDayEvents = events.filter((e) => e.isAllDay);
|
|
1212
|
+
const timedEvents = events.filter((e) => !e.isAllDay);
|
|
1213
|
+
const columns = dates.map((date, dayIdx) => {
|
|
1214
|
+
const dayStart = date.toZonedDateTime({
|
|
1215
|
+
timeZone: "UTC",
|
|
1216
|
+
plainTime: config.slotMinTime
|
|
1217
|
+
});
|
|
1218
|
+
const dayEnd = date.toZonedDateTime({
|
|
1219
|
+
timeZone: "UTC",
|
|
1220
|
+
plainTime: config.slotMaxTime
|
|
1221
|
+
});
|
|
1222
|
+
const columnEvents = timedEvents.filter((event) => {
|
|
1223
|
+
return compareZonedDateTimes(event.start, dayEnd) < 0 && compareZonedDateTimes(event.end, dayStart) > 0;
|
|
1224
|
+
});
|
|
1225
|
+
const positioned = solveLayout(columnEvents, config);
|
|
1226
|
+
return {
|
|
1227
|
+
date,
|
|
1228
|
+
slots: slotsPerDay[dayIdx] ?? [],
|
|
1229
|
+
events: positioned
|
|
1230
|
+
};
|
|
1231
|
+
});
|
|
1232
|
+
const allDayPositioned = allDayEvents.map((event, idx) => ({
|
|
1233
|
+
event,
|
|
1234
|
+
column: 0,
|
|
1235
|
+
totalColumns: 1,
|
|
1236
|
+
top: 0,
|
|
1237
|
+
height: 24,
|
|
1238
|
+
left: 0,
|
|
1239
|
+
width: 100,
|
|
1240
|
+
daySpan: 1,
|
|
1241
|
+
startSlotIndex: idx,
|
|
1242
|
+
endSlotIndex: idx,
|
|
1243
|
+
isDragging: false,
|
|
1244
|
+
isResizing: false,
|
|
1245
|
+
isSelected: false,
|
|
1246
|
+
isFocused: false
|
|
1247
|
+
}));
|
|
1248
|
+
return {
|
|
1249
|
+
viewType: "timeGrid",
|
|
1250
|
+
dateRange: config.visibleRange,
|
|
1251
|
+
columns,
|
|
1252
|
+
allDayRow: allDayPositioned,
|
|
1253
|
+
timeSlots: slotsPerDay[0] ?? [],
|
|
1254
|
+
nowPosition: getNowPosition(config)
|
|
1255
|
+
};
|
|
1256
|
+
};
|
|
1257
|
+
var timeGridPlugin = {
|
|
1258
|
+
name: "timeGrid",
|
|
1259
|
+
viewTypes: ["timeGrid"],
|
|
1260
|
+
layoutSolvers: {
|
|
1261
|
+
timeGrid: timeGridLayoutSolver
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
// src/plugins/timeline.ts
|
|
1266
|
+
var timelineLayoutSolver = (events, config, context) => {
|
|
1267
|
+
const spine = generateDateSpine(config);
|
|
1268
|
+
const resources = context.getResources();
|
|
1269
|
+
const lanes = resources.map((resource) => {
|
|
1270
|
+
const resourceEvents = events.filter((e) => e.resourceIds.includes(resource.id));
|
|
1271
|
+
const positioned = solveLayout(resourceEvents, config);
|
|
1272
|
+
return {
|
|
1273
|
+
resource,
|
|
1274
|
+
events: positioned
|
|
1275
|
+
};
|
|
1276
|
+
});
|
|
1277
|
+
const unassignedEvents = events.filter((e) => e.resourceIds.length === 0);
|
|
1278
|
+
if (unassignedEvents.length > 0) {
|
|
1279
|
+
lanes.push({
|
|
1280
|
+
resource: { id: "__unassigned__", title: "Unassigned" },
|
|
1281
|
+
events: solveLayout(unassignedEvents, config)
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
return {
|
|
1285
|
+
viewType: "timeline",
|
|
1286
|
+
dateRange: config.visibleRange,
|
|
1287
|
+
lanes,
|
|
1288
|
+
timeSlots: spine.slots?.[0] ?? [],
|
|
1289
|
+
nowPosition: getNowPosition(config)
|
|
1290
|
+
};
|
|
1291
|
+
};
|
|
1292
|
+
var timelinePlugin = {
|
|
1293
|
+
name: "timeline",
|
|
1294
|
+
viewTypes: ["timeline"],
|
|
1295
|
+
layoutSolvers: {
|
|
1296
|
+
timeline: timelineLayoutSolver
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
var agendaLayoutSolver = (events, config, _context) => {
|
|
1300
|
+
const groups = [];
|
|
1301
|
+
for (const date of iterateDays(config.visibleRange.start, config.visibleRange.end)) {
|
|
1302
|
+
const dateStart = date.toZonedDateTime({
|
|
1303
|
+
timeZone: "UTC",
|
|
1304
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
1305
|
+
});
|
|
1306
|
+
const dateEnd = date.add({ days: 1 }).toZonedDateTime({
|
|
1307
|
+
timeZone: "UTC",
|
|
1308
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
1309
|
+
});
|
|
1310
|
+
const dayEvents = events.filter(
|
|
1311
|
+
(event) => compareZonedDateTimes(event.start, dateEnd) < 0 && compareZonedDateTimes(event.end, dateStart) > 0
|
|
1312
|
+
).sort((a, b) => compareZonedDateTimes(a.start, b.start));
|
|
1313
|
+
if (dayEvents.length > 0) {
|
|
1314
|
+
groups.push({ date, events: dayEvents });
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return {
|
|
1318
|
+
viewType: "agenda",
|
|
1319
|
+
dateRange: config.visibleRange,
|
|
1320
|
+
groups
|
|
1321
|
+
};
|
|
1322
|
+
};
|
|
1323
|
+
var agendaPlugin = {
|
|
1324
|
+
name: "agenda",
|
|
1325
|
+
viewTypes: ["agenda"],
|
|
1326
|
+
layoutSolvers: {
|
|
1327
|
+
agenda: agendaLayoutSolver
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
function generateMonthGrids(startDate, monthCount, firstDayOfWeek, timezone) {
|
|
1331
|
+
const months = [];
|
|
1332
|
+
for (let i = 0; i < monthCount; i++) {
|
|
1333
|
+
const monthDate = startDate.add({ months: i });
|
|
1334
|
+
const monthStart = monthDate.with({ day: 1 });
|
|
1335
|
+
monthStart.add({ months: 1 });
|
|
1336
|
+
let gridStart = monthStart;
|
|
1337
|
+
while (gridStart.dayOfWeek % 7 !== firstDayOfWeek) {
|
|
1338
|
+
gridStart = gridStart.subtract({ days: 1 });
|
|
1339
|
+
}
|
|
1340
|
+
const weeks = [];
|
|
1341
|
+
let currentDay = gridStart;
|
|
1342
|
+
for (let w = 0; w < 6; w++) {
|
|
1343
|
+
const week = [];
|
|
1344
|
+
for (let d = 0; d < 7; d++) {
|
|
1345
|
+
week.push(currentDay);
|
|
1346
|
+
currentDay = currentDay.add({ days: 1 });
|
|
1347
|
+
}
|
|
1348
|
+
weeks.push(week);
|
|
1349
|
+
}
|
|
1350
|
+
months.push({ month: monthStart, weeks });
|
|
1351
|
+
}
|
|
1352
|
+
return months;
|
|
1353
|
+
}
|
|
1354
|
+
var multiMonthLayoutSolver = (events, config, context) => {
|
|
1355
|
+
const defaultMonthCount = config.preset === "multiMonthStack" ? 3 : 4;
|
|
1356
|
+
const monthCount = config.multiMonthMaxColumns ?? defaultMonthCount;
|
|
1357
|
+
const startDate = config.visibleRange.start.with({ day: 1 });
|
|
1358
|
+
const firstDayOfWeek = config.firstDayOfWeek ?? 0;
|
|
1359
|
+
const monthGrids = generateMonthGrids(startDate, monthCount, firstDayOfWeek);
|
|
1360
|
+
const monthLayouts = monthGrids.map(({ month, weeks }) => {
|
|
1361
|
+
const dayCells = solveDayGridLayout(
|
|
1362
|
+
events,
|
|
1363
|
+
weeks,
|
|
1364
|
+
config.eventMaxStack ?? 2,
|
|
1365
|
+
// Smaller max for multi-month
|
|
1366
|
+
"UTC"
|
|
1367
|
+
);
|
|
1368
|
+
return {
|
|
1369
|
+
month,
|
|
1370
|
+
weeks: dayCells
|
|
1371
|
+
};
|
|
1372
|
+
});
|
|
1373
|
+
return {
|
|
1374
|
+
viewType: "multiMonth",
|
|
1375
|
+
dateRange: config.visibleRange,
|
|
1376
|
+
months: monthLayouts
|
|
1377
|
+
};
|
|
1378
|
+
};
|
|
1379
|
+
var multiMonthPlugin = {
|
|
1380
|
+
name: "multiMonth",
|
|
1381
|
+
viewTypes: ["multiMonth"],
|
|
1382
|
+
layoutSolvers: {
|
|
1383
|
+
multiMonth: multiMonthLayoutSolver
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
var resourceTimeGridLayoutSolver = (events, config, context) => {
|
|
1387
|
+
const resources = context.getResources();
|
|
1388
|
+
const dates = Array.from(iterateDays(config.visibleRange.start, config.visibleRange.end));
|
|
1389
|
+
const slots = generateTimeSlots(dates[0] ?? config.visibleRange.start, config);
|
|
1390
|
+
const resourceLanes = resources.map((resource) => {
|
|
1391
|
+
const resourceEvents = events.filter(
|
|
1392
|
+
(event) => event.resourceIds?.includes(resource.id) ?? false
|
|
1393
|
+
);
|
|
1394
|
+
const dayColumns = dates.map((date) => {
|
|
1395
|
+
const dateStart = date.toZonedDateTime({
|
|
1396
|
+
timeZone: "UTC",
|
|
1397
|
+
plainTime: config.slotMinTime
|
|
1398
|
+
});
|
|
1399
|
+
const dateEnd = date.toZonedDateTime({
|
|
1400
|
+
timeZone: "UTC",
|
|
1401
|
+
plainTime: config.slotMaxTime
|
|
1402
|
+
});
|
|
1403
|
+
const dayEvents = resourceEvents.filter(
|
|
1404
|
+
(event) => compareZonedDateTimes(event.start, dateEnd) < 0 && compareZonedDateTimes(event.end, dateStart) > 0
|
|
1405
|
+
);
|
|
1406
|
+
return solveLayout(dayEvents, config);
|
|
1407
|
+
});
|
|
1408
|
+
return {
|
|
1409
|
+
resource,
|
|
1410
|
+
events: dayColumns.flat(),
|
|
1411
|
+
date: dates[0]
|
|
1412
|
+
};
|
|
1413
|
+
});
|
|
1414
|
+
return {
|
|
1415
|
+
viewType: "resourceTimeGrid",
|
|
1416
|
+
dateRange: config.visibleRange,
|
|
1417
|
+
slots,
|
|
1418
|
+
resourceLanes
|
|
1419
|
+
};
|
|
1420
|
+
};
|
|
1421
|
+
var verticalResourcePlugin = {
|
|
1422
|
+
name: "verticalResource",
|
|
1423
|
+
viewTypes: ["resourceTimeGrid"],
|
|
1424
|
+
layoutSolvers: {
|
|
1425
|
+
resourceTimeGrid: resourceTimeGridLayoutSolver
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
function calculateNewTimes(params) {
|
|
1429
|
+
const {
|
|
1430
|
+
operation,
|
|
1431
|
+
originalStartMinutes,
|
|
1432
|
+
originalEndMinutes,
|
|
1433
|
+
deltaMinutes,
|
|
1434
|
+
minHour,
|
|
1435
|
+
maxHour,
|
|
1436
|
+
snapMinutes
|
|
1437
|
+
} = params;
|
|
1438
|
+
const minMinutes = minHour * 60;
|
|
1439
|
+
const maxMinutes = maxHour * 60;
|
|
1440
|
+
const duration = originalEndMinutes - originalStartMinutes;
|
|
1441
|
+
let newStart;
|
|
1442
|
+
let newEnd;
|
|
1443
|
+
switch (operation) {
|
|
1444
|
+
case "move":
|
|
1445
|
+
newStart = snap(originalStartMinutes + deltaMinutes, snapMinutes);
|
|
1446
|
+
newEnd = newStart + duration;
|
|
1447
|
+
if (newStart < minMinutes) {
|
|
1448
|
+
newStart = minMinutes;
|
|
1449
|
+
newEnd = newStart + duration;
|
|
1450
|
+
}
|
|
1451
|
+
if (newEnd > maxMinutes) {
|
|
1452
|
+
newEnd = maxMinutes;
|
|
1453
|
+
newStart = newEnd - duration;
|
|
1454
|
+
}
|
|
1455
|
+
break;
|
|
1456
|
+
case "resize-start":
|
|
1457
|
+
newStart = snap(originalStartMinutes + deltaMinutes, snapMinutes);
|
|
1458
|
+
newEnd = originalEndMinutes;
|
|
1459
|
+
newStart = Math.max(minMinutes, Math.min(newStart, newEnd - snapMinutes));
|
|
1460
|
+
break;
|
|
1461
|
+
case "resize-end":
|
|
1462
|
+
newStart = originalStartMinutes;
|
|
1463
|
+
newEnd = snap(originalEndMinutes + deltaMinutes, snapMinutes);
|
|
1464
|
+
newEnd = Math.min(maxMinutes, Math.max(newEnd, newStart + snapMinutes));
|
|
1465
|
+
break;
|
|
1466
|
+
}
|
|
1467
|
+
return { start: newStart, end: newEnd };
|
|
1468
|
+
}
|
|
1469
|
+
function snap(value, gridSize) {
|
|
1470
|
+
return Math.round(value / gridSize) * gridSize;
|
|
1471
|
+
}
|
|
1472
|
+
function getSnapMinutes(config) {
|
|
1473
|
+
const snapDuration = config.snapDuration ?? config.slotDuration;
|
|
1474
|
+
return (snapDuration.hours || 0) * 60 + (snapDuration.minutes || 0) || 30;
|
|
1475
|
+
}
|
|
1476
|
+
function resolveDropTarget(yPercent, config) {
|
|
1477
|
+
const minMinutes = config.slotMinTime.hour * 60 + config.slotMinTime.minute;
|
|
1478
|
+
const maxMinutes = config.slotMaxTime.hour * 60 + config.slotMaxTime.minute;
|
|
1479
|
+
const totalMinutes = maxMinutes - minMinutes;
|
|
1480
|
+
const targetMinutes = minMinutes + yPercent / 100 * totalMinutes;
|
|
1481
|
+
const snapMinutes = getSnapMinutes(config);
|
|
1482
|
+
const snapped = snap(targetMinutes, snapMinutes);
|
|
1483
|
+
return {
|
|
1484
|
+
hour: Math.floor(snapped / 60),
|
|
1485
|
+
minute: snapped % 60
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
var interactionPlugin = {
|
|
1489
|
+
name: "interaction",
|
|
1490
|
+
interactionHandlers: [
|
|
1491
|
+
{
|
|
1492
|
+
type: "drag",
|
|
1493
|
+
onStart: (_info) => {
|
|
1494
|
+
},
|
|
1495
|
+
onMove: (_info) => {
|
|
1496
|
+
},
|
|
1497
|
+
onEnd: (info) => {
|
|
1498
|
+
if (!info.event) return { type: "create" };
|
|
1499
|
+
return {
|
|
1500
|
+
type: "move",
|
|
1501
|
+
event: info.event,
|
|
1502
|
+
newStart: void 0,
|
|
1503
|
+
// Computed by the React layer
|
|
1504
|
+
newEnd: void 0
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
type: "resize",
|
|
1510
|
+
onStart: (_info) => {
|
|
1511
|
+
},
|
|
1512
|
+
onMove: (_info) => {
|
|
1513
|
+
},
|
|
1514
|
+
onEnd: (info) => ({
|
|
1515
|
+
type: "resize",
|
|
1516
|
+
event: info.event,
|
|
1517
|
+
newStart: void 0,
|
|
1518
|
+
newEnd: void 0
|
|
1519
|
+
})
|
|
1520
|
+
},
|
|
1521
|
+
{
|
|
1522
|
+
type: "select",
|
|
1523
|
+
onEnd: (info) => ({
|
|
1524
|
+
type: "select",
|
|
1525
|
+
event: void 0,
|
|
1526
|
+
newStart: void 0,
|
|
1527
|
+
newEnd: void 0
|
|
1528
|
+
})
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
type: "click",
|
|
1532
|
+
onEnd: (info) => ({
|
|
1533
|
+
type: "select",
|
|
1534
|
+
event: info.event
|
|
1535
|
+
})
|
|
1536
|
+
}
|
|
1537
|
+
]
|
|
1538
|
+
};
|
|
1539
|
+
function isDropAllowed(event, newStart, newEnd, allEvents, constraints, viewConfig, resourceId) {
|
|
1540
|
+
return validateEventDrop(event, newStart, newEnd, allEvents, constraints, viewConfig, resourceId);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// src/plugins/registry.ts
|
|
1544
|
+
function createPluginRegistry(plugins) {
|
|
1545
|
+
const registry = {
|
|
1546
|
+
viewTypes: /* @__PURE__ */ new Set(),
|
|
1547
|
+
layoutSolvers: /* @__PURE__ */ new Map(),
|
|
1548
|
+
eventNormalizers: [],
|
|
1549
|
+
interactionHandlers: []
|
|
1550
|
+
};
|
|
1551
|
+
for (const plugin of plugins) {
|
|
1552
|
+
if (plugin.viewTypes) {
|
|
1553
|
+
for (const vt of plugin.viewTypes) {
|
|
1554
|
+
registry.viewTypes.add(vt);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
if (plugin.layoutSolvers) {
|
|
1558
|
+
for (const [viewType, solver] of Object.entries(plugin.layoutSolvers)) {
|
|
1559
|
+
if (solver) {
|
|
1560
|
+
registry.layoutSolvers.set(viewType, solver);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
if (plugin.eventNormalizers) {
|
|
1565
|
+
registry.eventNormalizers.push(...plugin.eventNormalizers);
|
|
1566
|
+
}
|
|
1567
|
+
if (plugin.interactionHandlers) {
|
|
1568
|
+
registry.interactionHandlers.push(...plugin.interactionHandlers);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return registry;
|
|
1572
|
+
}
|
|
1573
|
+
function resolveLayoutSolver(viewType, registry) {
|
|
1574
|
+
return registry.layoutSolvers.get(viewType);
|
|
1575
|
+
}
|
|
1576
|
+
function applyEventNormalizers(events, registry, context) {
|
|
1577
|
+
let result = [...events];
|
|
1578
|
+
for (const normalizer of registry.eventNormalizers) {
|
|
1579
|
+
const expanded = [];
|
|
1580
|
+
for (const event of result) {
|
|
1581
|
+
const output = normalizer(event, context);
|
|
1582
|
+
if (Array.isArray(output)) {
|
|
1583
|
+
expanded.push(...output);
|
|
1584
|
+
} else {
|
|
1585
|
+
expanded.push(output);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
result = expanded;
|
|
1589
|
+
}
|
|
1590
|
+
return result;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
exports.EventStore = EventStore;
|
|
1594
|
+
exports.addDurationToDateTime = addDurationToDateTime;
|
|
1595
|
+
exports.agendaPlugin = agendaPlugin;
|
|
1596
|
+
exports.applyEventNormalizers = applyEventNormalizers;
|
|
1597
|
+
exports.calculateNewTimes = calculateNewTimes;
|
|
1598
|
+
exports.clampDateToRange = clampDateToRange;
|
|
1599
|
+
exports.clampToValidRange = clampToValidRange;
|
|
1600
|
+
exports.compareDateTimes = compareDateTimes;
|
|
1601
|
+
exports.compareDates = compareDates;
|
|
1602
|
+
exports.compareTimes = compareTimes;
|
|
1603
|
+
exports.compareZonedDateTimes = compareZonedDateTimes;
|
|
1604
|
+
exports.createDateRange = createDateRange;
|
|
1605
|
+
exports.createEventSources = createEventSources;
|
|
1606
|
+
exports.createPluginRegistry = createPluginRegistry;
|
|
1607
|
+
exports.dayGridPlugin = dayGridPlugin;
|
|
1608
|
+
exports.daysBetween = daysBetween;
|
|
1609
|
+
exports.durationBetween = durationBetween;
|
|
1610
|
+
exports.eventToPlainObject = eventToPlainObject;
|
|
1611
|
+
exports.expandRRule = expandRRule;
|
|
1612
|
+
exports.fetchEventSource = fetchEventSource;
|
|
1613
|
+
exports.formatDate = formatDate;
|
|
1614
|
+
exports.formatDateRange = formatDateRange;
|
|
1615
|
+
exports.formatDayHeader = formatDayHeader;
|
|
1616
|
+
exports.formatEventTime = formatEventTime;
|
|
1617
|
+
exports.formatSlotLabel = formatSlotLabel;
|
|
1618
|
+
exports.formatTime = formatTime;
|
|
1619
|
+
exports.fromISODayOfWeek = fromISODayOfWeek;
|
|
1620
|
+
exports.generateDateSpine = generateDateSpine;
|
|
1621
|
+
exports.generateTimeSlots = generateTimeSlots;
|
|
1622
|
+
exports.getDaysInMonth = getDaysInMonth;
|
|
1623
|
+
exports.getMonthEnd = getMonthEnd;
|
|
1624
|
+
exports.getMonthStart = getMonthStart;
|
|
1625
|
+
exports.getMonthVisibleRange = getMonthVisibleRange;
|
|
1626
|
+
exports.getNowPosition = getNowPosition;
|
|
1627
|
+
exports.getSlotIndex = getSlotIndex;
|
|
1628
|
+
exports.getSnapMinutes = getSnapMinutes;
|
|
1629
|
+
exports.getToday = getToday2;
|
|
1630
|
+
exports.getTodayDate = getToday;
|
|
1631
|
+
exports.getViewTitle = getViewTitle;
|
|
1632
|
+
exports.getVisibleRange = getVisibleRange;
|
|
1633
|
+
exports.getWeekEnd = getWeekEnd;
|
|
1634
|
+
exports.getWeekNumber = getWeekNumber;
|
|
1635
|
+
exports.getWeekStart = getWeekStart;
|
|
1636
|
+
exports.interactionPlugin = interactionPlugin;
|
|
1637
|
+
exports.invariant = invariant;
|
|
1638
|
+
exports.isDropAllowed = isDropAllowed;
|
|
1639
|
+
exports.isRtl = isRtl;
|
|
1640
|
+
exports.isWeekend = isWeekend;
|
|
1641
|
+
exports.isWithinValidRange = isWithinValidRange;
|
|
1642
|
+
exports.iterateDays = iterateDays;
|
|
1643
|
+
exports.mergeEventSources = mergeEventSources;
|
|
1644
|
+
exports.multiMonthPlugin = multiMonthPlugin;
|
|
1645
|
+
exports.navigateNext = navigateNext;
|
|
1646
|
+
exports.navigatePrev = navigatePrev;
|
|
1647
|
+
exports.normalizeEvent = normalizeEvent;
|
|
1648
|
+
exports.normalizeEventSourceDef = normalizeEventSourceDef;
|
|
1649
|
+
exports.parseRRule = parseRRule;
|
|
1650
|
+
exports.rangeContains = rangeContains;
|
|
1651
|
+
exports.rangesOverlap = rangesOverlap;
|
|
1652
|
+
exports.resolveDropTarget = resolveDropTarget;
|
|
1653
|
+
exports.resolveLayoutSolver = resolveLayoutSolver;
|
|
1654
|
+
exports.snapToSlot = snapToSlot;
|
|
1655
|
+
exports.solveDayGridLayout = solveDayGridLayout;
|
|
1656
|
+
exports.solveLayout = solveLayout;
|
|
1657
|
+
exports.solveLayoutFull = solveLayoutFull;
|
|
1658
|
+
exports.timeGridPlugin = timeGridPlugin;
|
|
1659
|
+
exports.timelinePlugin = timelinePlugin;
|
|
1660
|
+
exports.toISODayOfWeek = toISODayOfWeek;
|
|
1661
|
+
exports.validateEventDrop = validateEventDrop;
|
|
1662
|
+
exports.validateSelection = validateSelection;
|
|
1663
|
+
exports.verticalResourcePlugin = verticalResourcePlugin;
|
|
1664
|
+
exports.viewTypeFromPreset = viewTypeFromPreset;
|
|
1665
|
+
exports.viewUnitFromPreset = viewUnitFromPreset;
|
|
1666
|
+
exports.zonedRangeContains = zonedRangeContains;
|
|
1667
|
+
exports.zonedRangesOverlap = zonedRangesOverlap;
|
|
1668
|
+
//# sourceMappingURL=index.cjs.map
|
|
1669
|
+
//# sourceMappingURL=index.cjs.map
|