@svar-ui/calendar-store 2.6.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/dist/index.d.mts +416 -0
- package/dist/index.mjs +1245 -0
- package/license.txt +21 -0
- package/package.json +39 -0
- package/readme.md +5 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1245 @@
|
|
|
1
|
+
import { DataRouter, EventBus, Store, tempID } from "@svar-ui/lib-state";
|
|
2
|
+
|
|
3
|
+
//#region package.json
|
|
4
|
+
var version$1 = "2.6.0";
|
|
5
|
+
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/events_store.ts
|
|
8
|
+
var EventsStore = class {
|
|
9
|
+
events = [];
|
|
10
|
+
constructor(initialEvents) {
|
|
11
|
+
if (initialEvents) for (const ev of initialEvents) this.addEvent(ev);
|
|
12
|
+
}
|
|
13
|
+
addEvent(event) {
|
|
14
|
+
const id = event.id ?? tempID();
|
|
15
|
+
const full = {
|
|
16
|
+
...event,
|
|
17
|
+
id
|
|
18
|
+
};
|
|
19
|
+
this.events.push(full);
|
|
20
|
+
return full;
|
|
21
|
+
}
|
|
22
|
+
updateEvent(id, updates, _mode, _originalDate) {
|
|
23
|
+
const idx = this.events.findIndex((e) => e.id === id);
|
|
24
|
+
if (idx === -1) return null;
|
|
25
|
+
const existing = this.events[idx];
|
|
26
|
+
const updated = {
|
|
27
|
+
...existing,
|
|
28
|
+
...updates,
|
|
29
|
+
id: existing.id
|
|
30
|
+
};
|
|
31
|
+
this.events[idx] = updated;
|
|
32
|
+
return updated;
|
|
33
|
+
}
|
|
34
|
+
removeEvent(id) {
|
|
35
|
+
const idx = this.events.findIndex((e) => e.id === id);
|
|
36
|
+
if (idx === -1) return false;
|
|
37
|
+
this.events.splice(idx, 1);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
getEvent(id) {
|
|
41
|
+
return this.events.find((e) => e.id === id);
|
|
42
|
+
}
|
|
43
|
+
getEvents(start, end) {
|
|
44
|
+
if (!start && !end) return [...this.events];
|
|
45
|
+
return this.events.filter((e) => {
|
|
46
|
+
if (start && !(e.end > start)) return false;
|
|
47
|
+
if (end && !(e.start < end)) return false;
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
clear() {
|
|
52
|
+
this.events = [];
|
|
53
|
+
}
|
|
54
|
+
getCount() {
|
|
55
|
+
return this.events.length;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/models/helpers/scales.ts
|
|
61
|
+
const DAY_MS$1 = 1440 * 60 * 1e3;
|
|
62
|
+
function midnight(date) {
|
|
63
|
+
const d = new Date(date);
|
|
64
|
+
d.setHours(0, 0, 0, 0);
|
|
65
|
+
return d;
|
|
66
|
+
}
|
|
67
|
+
var LinearScale = class {
|
|
68
|
+
rangeStart;
|
|
69
|
+
rangeEnd;
|
|
70
|
+
stepMs;
|
|
71
|
+
snapStepMs;
|
|
72
|
+
unitCount;
|
|
73
|
+
units;
|
|
74
|
+
discrete;
|
|
75
|
+
constructor(rangeStart, rangeEnd, unitCount, stepMs, format, ui, discrete, snapStepMs) {
|
|
76
|
+
this.rangeStart = rangeStart;
|
|
77
|
+
this.rangeEnd = rangeEnd;
|
|
78
|
+
this.unitCount = unitCount;
|
|
79
|
+
this.stepMs = stepMs;
|
|
80
|
+
this.snapStepMs = snapStepMs ?? stepMs;
|
|
81
|
+
this.discrete = discrete ?? false;
|
|
82
|
+
const size = 100 / unitCount;
|
|
83
|
+
this.units = [];
|
|
84
|
+
const stepDays = stepMs >= DAY_MS$1 ? Math.round(stepMs / DAY_MS$1) : 0;
|
|
85
|
+
const markWeekend = stepMs === DAY_MS$1 && unitCount > 1;
|
|
86
|
+
for (let i = 0; i < unitCount; i++) {
|
|
87
|
+
let unitStart;
|
|
88
|
+
if (stepDays > 0) {
|
|
89
|
+
unitStart = new Date(rangeStart);
|
|
90
|
+
unitStart.setDate(unitStart.getDate() + i * stepDays);
|
|
91
|
+
} else unitStart = new Date(rangeStart.getTime() + i * stepMs);
|
|
92
|
+
const unit = {
|
|
93
|
+
id: this.formatId(unitStart),
|
|
94
|
+
label: format(unitStart),
|
|
95
|
+
position: i * size,
|
|
96
|
+
size,
|
|
97
|
+
ui: {
|
|
98
|
+
...ui,
|
|
99
|
+
date: unitStart
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
if (markWeekend) {
|
|
103
|
+
const dow = unitStart.getDay();
|
|
104
|
+
unit.weekend = dow === 0 || dow === 6;
|
|
105
|
+
}
|
|
106
|
+
this.units.push(unit);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
formatId(date) {
|
|
110
|
+
if (this.stepMs >= DAY_MS$1) return date.toISOString().slice(0, 10);
|
|
111
|
+
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
112
|
+
}
|
|
113
|
+
get count() {
|
|
114
|
+
return this.unitCount;
|
|
115
|
+
}
|
|
116
|
+
eventToPosition(event) {
|
|
117
|
+
const range = this.rangeEnd.getTime() - this.rangeStart.getTime();
|
|
118
|
+
return {
|
|
119
|
+
start: (event.start.getTime() - this.rangeStart.getTime()) / range * 100,
|
|
120
|
+
end: (event.end.getTime() - this.rangeStart.getTime()) / range * 100
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
contains(date) {
|
|
124
|
+
return date >= this.rangeStart && date < this.rangeEnd;
|
|
125
|
+
}
|
|
126
|
+
positionToValue(position) {
|
|
127
|
+
if (this.discrete) {
|
|
128
|
+
const raw = position / (100 / this.unitCount);
|
|
129
|
+
const idx = Math.max(0, Math.min(Math.floor(raw + 1e-9), this.unitCount - 1));
|
|
130
|
+
return new Date(this.rangeStart.getTime() + idx * this.stepMs);
|
|
131
|
+
}
|
|
132
|
+
const range = this.rangeEnd.getTime() - this.rangeStart.getTime();
|
|
133
|
+
return new Date(this.rangeStart.getTime() + position / 100 * range);
|
|
134
|
+
}
|
|
135
|
+
getHeaders() {
|
|
136
|
+
return [this.units];
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var DiscreteScale = class {
|
|
140
|
+
items;
|
|
141
|
+
accessor;
|
|
142
|
+
boxSize;
|
|
143
|
+
units;
|
|
144
|
+
constructor(items, accessor, ui) {
|
|
145
|
+
this.items = items;
|
|
146
|
+
this.accessor = accessor;
|
|
147
|
+
this.boxSize = 100 / items.length;
|
|
148
|
+
this.units = items.map((item, i) => ({
|
|
149
|
+
id: item.id,
|
|
150
|
+
label: item.label,
|
|
151
|
+
position: i * this.boxSize,
|
|
152
|
+
size: this.boxSize,
|
|
153
|
+
ui
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
get count() {
|
|
157
|
+
return this.items.length;
|
|
158
|
+
}
|
|
159
|
+
eventToPosition(event) {
|
|
160
|
+
const id = this.accessor.get(event);
|
|
161
|
+
const idx = this.items.findIndex((item) => item.id === id);
|
|
162
|
+
if (idx === -1) return {
|
|
163
|
+
start: -1,
|
|
164
|
+
end: -1
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
start: idx * this.boxSize,
|
|
168
|
+
end: (idx + 1) * this.boxSize
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
contains() {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
positionToValue(position) {
|
|
175
|
+
const idx = Math.max(0, Math.min(Math.floor(position / this.boxSize), this.items.length - 1));
|
|
176
|
+
return this.items[idx].id;
|
|
177
|
+
}
|
|
178
|
+
getHeaders() {
|
|
179
|
+
return [this.units];
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
function resolveAccessor(accessor) {
|
|
183
|
+
if (typeof accessor === "string") return {
|
|
184
|
+
get: (event) => event[accessor],
|
|
185
|
+
set: (event, id) => ({
|
|
186
|
+
...event,
|
|
187
|
+
[accessor]: id
|
|
188
|
+
})
|
|
189
|
+
};
|
|
190
|
+
return accessor;
|
|
191
|
+
}
|
|
192
|
+
function createScale(config, startDate, fmt) {
|
|
193
|
+
switch (config.type) {
|
|
194
|
+
case "date": {
|
|
195
|
+
const c = config;
|
|
196
|
+
const step = c.step ?? 1;
|
|
197
|
+
const rangeStart = midnight(startDate);
|
|
198
|
+
const rangeEnd = new Date(rangeStart.getTime() + c.length * step * DAY_MS$1);
|
|
199
|
+
const stepMs = step * DAY_MS$1;
|
|
200
|
+
const snapStepMs = c.snapStep === false ? false : (c.snapStep ?? step) * DAY_MS$1;
|
|
201
|
+
const format = c.format && fmt ? fmt(c.format) : (d) => d.toLocaleDateString("en-US", { weekday: "short" });
|
|
202
|
+
return new LinearScale(rangeStart, rangeEnd, c.length, stepMs, format, c.ui, c.discrete, snapStepMs);
|
|
203
|
+
}
|
|
204
|
+
case "time": {
|
|
205
|
+
const c = config;
|
|
206
|
+
if (c.segments) throw new Error("SegmentedScale not implemented");
|
|
207
|
+
const startHour = c.startHour ?? 0;
|
|
208
|
+
const endHour = c.endHour ?? 24;
|
|
209
|
+
const stepMin = c.step ?? 60;
|
|
210
|
+
const rangeStart = new Date(startDate);
|
|
211
|
+
rangeStart.setHours(startHour, 0, 0, 0);
|
|
212
|
+
const rangeEnd = new Date(startDate);
|
|
213
|
+
rangeEnd.setHours(endHour, 0, 0, 0);
|
|
214
|
+
const stepMs = stepMin * 60 * 1e3;
|
|
215
|
+
const snapStepMs = c.snapStep === false ? false : (c.snapStep ?? stepMin) * 60 * 1e3;
|
|
216
|
+
return new LinearScale(rangeStart, rangeEnd, (endHour - startHour) * 60 / stepMin, stepMs, c.format && fmt ? fmt(c.format) : (d) => d.toLocaleTimeString("en-US", {
|
|
217
|
+
hour: "2-digit",
|
|
218
|
+
minute: "2-digit"
|
|
219
|
+
}), c.ui, void 0, snapStepMs);
|
|
220
|
+
}
|
|
221
|
+
case "unit": {
|
|
222
|
+
const c = config;
|
|
223
|
+
return new DiscreteScale(c.items, resolveAccessor(c.accessor), c.ui);
|
|
224
|
+
}
|
|
225
|
+
case "combined":
|
|
226
|
+
case "stacked": throw new Error(`${config.type} scale not implemented`);
|
|
227
|
+
default: throw new Error(`Unknown scale type`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/models/helpers/layout.ts
|
|
233
|
+
function layoutBars(primitives) {
|
|
234
|
+
const valid = primitives.filter((p) => p.width > 0);
|
|
235
|
+
if (valid.length === 0) return {
|
|
236
|
+
primitives: [],
|
|
237
|
+
totalLanes: 0
|
|
238
|
+
};
|
|
239
|
+
const lanes = [];
|
|
240
|
+
const result = [];
|
|
241
|
+
for (const p of valid) {
|
|
242
|
+
let assigned = -1;
|
|
243
|
+
for (let i = 0; i < lanes.length; i++) if (lanes[i] <= p.x + 1e-9) {
|
|
244
|
+
assigned = i;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
if (assigned === -1) {
|
|
248
|
+
assigned = lanes.length;
|
|
249
|
+
lanes.push(0);
|
|
250
|
+
}
|
|
251
|
+
lanes[assigned] = p.x + p.width;
|
|
252
|
+
result.push({
|
|
253
|
+
...p,
|
|
254
|
+
lane: assigned,
|
|
255
|
+
totalLanes: 0
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
const totalLanes = lanes.length;
|
|
259
|
+
for (const p of result) p.totalLanes = totalLanes;
|
|
260
|
+
return {
|
|
261
|
+
primitives: result,
|
|
262
|
+
totalLanes
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function layoutBoxes(primitives) {
|
|
266
|
+
const valid = primitives.filter((p) => p.height > 0);
|
|
267
|
+
if (valid.length === 0) return { primitives: [] };
|
|
268
|
+
const groups = [];
|
|
269
|
+
let currentGroup = [];
|
|
270
|
+
let groupEnd = -Infinity;
|
|
271
|
+
for (const p of valid) if (currentGroup.length === 0 || p.y < groupEnd) {
|
|
272
|
+
currentGroup.push(p);
|
|
273
|
+
groupEnd = Math.max(groupEnd, p.y + p.height);
|
|
274
|
+
} else {
|
|
275
|
+
groups.push(currentGroup);
|
|
276
|
+
currentGroup = [p];
|
|
277
|
+
groupEnd = p.y + p.height;
|
|
278
|
+
}
|
|
279
|
+
if (currentGroup.length > 0) groups.push(currentGroup);
|
|
280
|
+
const result = [];
|
|
281
|
+
for (const group of groups) {
|
|
282
|
+
const slots = [];
|
|
283
|
+
for (const p of group) {
|
|
284
|
+
let assigned = -1;
|
|
285
|
+
for (let i = 0; i < slots.length; i++) if (slots[i] <= p.y) {
|
|
286
|
+
assigned = i;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
if (assigned === -1) {
|
|
290
|
+
assigned = slots.length;
|
|
291
|
+
slots.push(0);
|
|
292
|
+
}
|
|
293
|
+
slots[assigned] = p.y + p.height;
|
|
294
|
+
result.push({
|
|
295
|
+
...p,
|
|
296
|
+
slot: assigned,
|
|
297
|
+
maxConcurrency: 0
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
const maxConcurrency = slots.length;
|
|
301
|
+
for (let i = result.length - group.length; i < result.length; i++) result[i].maxConcurrency = maxConcurrency;
|
|
302
|
+
}
|
|
303
|
+
return { primitives: result };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/models/helpers/filters.ts
|
|
308
|
+
function isMultiDay(event) {
|
|
309
|
+
if (event.allDay) return true;
|
|
310
|
+
const s = event.start;
|
|
311
|
+
const e = event.end;
|
|
312
|
+
return s.getFullYear() !== e.getFullYear() || s.getMonth() !== e.getMonth() || s.getDate() !== e.getDate();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/models/model.ts
|
|
317
|
+
var ViewModel = class {
|
|
318
|
+
render;
|
|
319
|
+
weekStartDay = 1;
|
|
320
|
+
fmt = () => (d) => d.toLocaleDateString();
|
|
321
|
+
startDate;
|
|
322
|
+
endDate;
|
|
323
|
+
cachedSections = [];
|
|
324
|
+
_sectionOverrides;
|
|
325
|
+
configure(sections) {
|
|
326
|
+
this._sectionOverrides = sections;
|
|
327
|
+
}
|
|
328
|
+
setRange(date) {
|
|
329
|
+
this.startDate = this.rangeStart(date);
|
|
330
|
+
this.endDate = this.addRange(this.startDate, 1);
|
|
331
|
+
return [this.startDate, this.endDate];
|
|
332
|
+
}
|
|
333
|
+
process(events) {
|
|
334
|
+
let sections = this.getSections();
|
|
335
|
+
if (this._sectionOverrides) sections = sections.map((s) => {
|
|
336
|
+
const ov = this._sectionOverrides[s.name];
|
|
337
|
+
return ov ? deepMerge(s, ov) : s;
|
|
338
|
+
});
|
|
339
|
+
this.cachedSections = [];
|
|
340
|
+
const results = [];
|
|
341
|
+
for (const section of sections) {
|
|
342
|
+
const result = this.processSection(section, events, true);
|
|
343
|
+
results.push(result);
|
|
344
|
+
}
|
|
345
|
+
return results;
|
|
346
|
+
}
|
|
347
|
+
toPositionStart(sectionName, x, y, ev, snap) {
|
|
348
|
+
return this.resolvePosition(sectionName, x, y, "start", ev, snap);
|
|
349
|
+
}
|
|
350
|
+
toPositionEnd(sectionName, x, y, ev, snap) {
|
|
351
|
+
return this.resolvePosition(sectionName, x, y, "end", ev, snap);
|
|
352
|
+
}
|
|
353
|
+
resolvePosition(sectionName, x, y, target, ev, snap) {
|
|
354
|
+
const cached = this.cachedSections.find((c) => c.section.name === sectionName);
|
|
355
|
+
if (!cached) return ev ? { ...ev } : {};
|
|
356
|
+
const { section } = cached;
|
|
357
|
+
const primaryAxis = this.getPrimaryAxis(section);
|
|
358
|
+
const primaryPos = primaryAxis === "x" ? x : y;
|
|
359
|
+
const secondaryPos = primaryAxis === "x" ? y : x;
|
|
360
|
+
const result = ev ? { ...ev } : {};
|
|
361
|
+
const primaryVal = cached.primaryScale.positionToValue(primaryPos);
|
|
362
|
+
const unitIdx = this.findUnitForPosition(cached.primaryScale, primaryPos);
|
|
363
|
+
const unit = cached.primaryScale.units[unitIdx];
|
|
364
|
+
let secScale = cached.secondaryScales.get(unitIdx);
|
|
365
|
+
if (!secScale) {
|
|
366
|
+
const secondaryConfig = primaryAxis === "x" ? section.yScale : section.xScale;
|
|
367
|
+
const groupStartVal = cached.primaryScale.positionToValue(unit.position);
|
|
368
|
+
secScale = createScale(secondaryConfig, groupStartVal instanceof Date ? groupStartVal : this.startDate);
|
|
369
|
+
cached.secondaryScales.set(unitIdx, secScale);
|
|
370
|
+
}
|
|
371
|
+
const secondaryVal = secScale.positionToValue(secondaryPos);
|
|
372
|
+
if (primaryVal instanceof Date && secondaryVal instanceof Date) {
|
|
373
|
+
const secStepMs = secScale.stepMs || 3600 * 1e3;
|
|
374
|
+
if (secStepMs >= DAY_MS) {
|
|
375
|
+
const combined = new Date(secondaryVal);
|
|
376
|
+
const src = ev?.[target];
|
|
377
|
+
if (src instanceof Date) combined.setHours(src.getHours(), src.getMinutes(), src.getSeconds(), src.getMilliseconds());
|
|
378
|
+
result[target] = combined;
|
|
379
|
+
} else {
|
|
380
|
+
const combined = new Date(primaryVal);
|
|
381
|
+
combined.setHours(secondaryVal.getHours(), secondaryVal.getMinutes(), secondaryVal.getSeconds(), secondaryVal.getMilliseconds());
|
|
382
|
+
const snapMs = secScale.snapStepMs ?? secStepMs;
|
|
383
|
+
if (snap && snapMs !== false) {
|
|
384
|
+
const ms = combined.getTime();
|
|
385
|
+
const base = secScale.rangeStart?.getTime() || 0;
|
|
386
|
+
const snapped = base + Math.round((ms - base) / snapMs) * snapMs;
|
|
387
|
+
result[target] = new Date(snapped);
|
|
388
|
+
} else result[target] = combined;
|
|
389
|
+
}
|
|
390
|
+
} else if (primaryVal instanceof Date) {
|
|
391
|
+
if (snap) {
|
|
392
|
+
const rawStep = cached.primaryScale.snapStepMs ?? cached.primaryScale.stepMs ?? DAY_MS;
|
|
393
|
+
const base = cached.primaryScale.rangeStart?.getTime();
|
|
394
|
+
if (rawStep !== false && base != null) {
|
|
395
|
+
const snapped = base + Math.round((primaryVal.getTime() - base) / rawStep) * rawStep;
|
|
396
|
+
result[target] = new Date(snapped);
|
|
397
|
+
} else result[target] = primaryVal;
|
|
398
|
+
} else result[target] = primaryVal;
|
|
399
|
+
const src = ev?.[target];
|
|
400
|
+
if (src instanceof Date) result[target].setHours(src.getHours(), src.getMinutes(), src.getSeconds(), src.getMilliseconds());
|
|
401
|
+
if (typeof secondaryVal !== "object") {
|
|
402
|
+
const acc = secScale.accessor;
|
|
403
|
+
if (acc?.set) Object.assign(result, acc.set(result, secondaryVal));
|
|
404
|
+
}
|
|
405
|
+
} else if (secondaryVal instanceof Date) {
|
|
406
|
+
result[target] = secondaryVal;
|
|
407
|
+
const acc = cached.primaryScale.accessor;
|
|
408
|
+
if (acc?.set) Object.assign(result, acc.set(result, primaryVal));
|
|
409
|
+
}
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
processSection(section, events, doLayout) {
|
|
413
|
+
const primaryAxis = this.getPrimaryAxis(section);
|
|
414
|
+
const primaryConfig = primaryAxis === "x" ? section.xScale : section.yScale;
|
|
415
|
+
const secondaryConfig = primaryAxis === "x" ? section.yScale : section.xScale;
|
|
416
|
+
const primaryScale = createScale(primaryConfig, this.startDate, this.fmt);
|
|
417
|
+
const filtered = section.filter ? events.filter(section.filter) : events;
|
|
418
|
+
const allChunks = [];
|
|
419
|
+
for (const event of filtered) {
|
|
420
|
+
const chunks = this.splitEvent(event, primaryScale, primaryConfig);
|
|
421
|
+
allChunks.push(...chunks);
|
|
422
|
+
}
|
|
423
|
+
const secondaryScales = /* @__PURE__ */ new Map();
|
|
424
|
+
const allPrimitives = [];
|
|
425
|
+
const unitGroups = /* @__PURE__ */ new Map();
|
|
426
|
+
for (const chunk of allChunks) {
|
|
427
|
+
const unitIdx = this.findUnitIndex(primaryScale, {
|
|
428
|
+
...chunk.event,
|
|
429
|
+
start: chunk.start,
|
|
430
|
+
end: chunk.end
|
|
431
|
+
});
|
|
432
|
+
if (unitIdx === -1) continue;
|
|
433
|
+
let group = unitGroups.get(unitIdx);
|
|
434
|
+
if (!group) {
|
|
435
|
+
group = [];
|
|
436
|
+
unitGroups.set(unitIdx, group);
|
|
437
|
+
}
|
|
438
|
+
group.push(chunk);
|
|
439
|
+
}
|
|
440
|
+
for (const [unitIdx, chunks] of unitGroups) {
|
|
441
|
+
const unit = primaryScale.units[unitIdx];
|
|
442
|
+
const groupStartVal = primaryScale.positionToValue(unit.position);
|
|
443
|
+
const secScale = createScale(secondaryConfig, groupStartVal instanceof Date ? groupStartVal : this.startDate, this.fmt);
|
|
444
|
+
secondaryScales.set(unitIdx, secScale);
|
|
445
|
+
const primitives = [];
|
|
446
|
+
for (const chunk of chunks) {
|
|
447
|
+
const prim = this.mapToPrimitive(chunk, unit, secScale, primaryAxis);
|
|
448
|
+
if (prim) primitives.push(prim);
|
|
449
|
+
}
|
|
450
|
+
if (doLayout && primitives.length > 0) {
|
|
451
|
+
this.sortBeforeLayout(primitives, section.mode);
|
|
452
|
+
if (section.mode === "bars" || section.mode === "grid") {
|
|
453
|
+
const laid = layoutBars(primitives);
|
|
454
|
+
allPrimitives.push(...laid.primitives);
|
|
455
|
+
} else {
|
|
456
|
+
const laid = layoutBoxes(primitives);
|
|
457
|
+
allPrimitives.push(...laid.primitives);
|
|
458
|
+
}
|
|
459
|
+
} else allPrimitives.push(...primitives);
|
|
460
|
+
}
|
|
461
|
+
if (secondaryScales.size === 0) {
|
|
462
|
+
const fallbackStart = primaryScale.units.length > 0 ? primaryScale.positionToValue(primaryScale.units[0].position) : this.startDate;
|
|
463
|
+
const groupStart = fallbackStart instanceof Date ? fallbackStart : this.startDate;
|
|
464
|
+
secondaryScales.set(0, createScale(secondaryConfig, groupStart, this.fmt));
|
|
465
|
+
}
|
|
466
|
+
const xScale = primaryAxis === "x" ? primaryScale : secondaryScales.values().next().value;
|
|
467
|
+
const yScale = primaryAxis === "y" ? primaryScale : secondaryScales.values().next().value;
|
|
468
|
+
const xHeaders = xScale ? xScale.getHeaders() : null;
|
|
469
|
+
const yHeaders = yScale ? yScale.getHeaders() : null;
|
|
470
|
+
const cells = section.mode === "grid" ? this.buildCells(xScale, yScale) : void 0;
|
|
471
|
+
this.cachedSections.push({
|
|
472
|
+
section,
|
|
473
|
+
primaryScale,
|
|
474
|
+
secondaryScales,
|
|
475
|
+
xHeaders,
|
|
476
|
+
yHeaders,
|
|
477
|
+
cells
|
|
478
|
+
});
|
|
479
|
+
const ui = {
|
|
480
|
+
drag: section.mode === "boxes" || section.mode === "bars" || section.mode === "grid",
|
|
481
|
+
dragCreate: section.mode === "boxes" || section.mode === "bars" || section.mode === "grid",
|
|
482
|
+
...section.mode === "boxes" ? { boxLayout: section.boxLayout ?? "split" } : {},
|
|
483
|
+
...section.ui
|
|
484
|
+
};
|
|
485
|
+
return {
|
|
486
|
+
name: section.name,
|
|
487
|
+
mode: section.mode,
|
|
488
|
+
size: section.size ?? 1,
|
|
489
|
+
primitives: allPrimitives,
|
|
490
|
+
xHeaders,
|
|
491
|
+
yHeaders,
|
|
492
|
+
xVisible: section.xScale.visible,
|
|
493
|
+
yVisible: section.yScale.visible,
|
|
494
|
+
cells,
|
|
495
|
+
ui
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
buildCells(_xScale, _yScale) {
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
sortBeforeLayout(primitives, mode) {
|
|
502
|
+
if (mode === "bars" || mode === "grid") primitives.sort((a, b) => a.x - b.x);
|
|
503
|
+
else primitives.sort((a, b) => a.y - b.y);
|
|
504
|
+
}
|
|
505
|
+
getPrimaryAxis(section) {
|
|
506
|
+
if (section.primaryScale) return section.primaryScale;
|
|
507
|
+
return section.mode === "boxes" ? "x" : "y";
|
|
508
|
+
}
|
|
509
|
+
splitEvent(event, primaryScale, _config) {
|
|
510
|
+
const units = primaryScale.units;
|
|
511
|
+
if (units.length <= 1) return [{
|
|
512
|
+
id: event.id,
|
|
513
|
+
event,
|
|
514
|
+
start: event.start,
|
|
515
|
+
end: event.end
|
|
516
|
+
}];
|
|
517
|
+
const chunks = [];
|
|
518
|
+
const boundaries = [];
|
|
519
|
+
for (let i = 1; i < units.length; i++) {
|
|
520
|
+
const d = primaryScale.positionToValue(units[i].position);
|
|
521
|
+
if (d instanceof Date) boundaries.push(d);
|
|
522
|
+
}
|
|
523
|
+
let currentStart = event.start;
|
|
524
|
+
for (const boundary of boundaries) {
|
|
525
|
+
if (boundary <= currentStart) continue;
|
|
526
|
+
if (boundary >= event.end) break;
|
|
527
|
+
chunks.push({
|
|
528
|
+
id: event.id,
|
|
529
|
+
event,
|
|
530
|
+
start: currentStart,
|
|
531
|
+
end: boundary
|
|
532
|
+
});
|
|
533
|
+
currentStart = boundary;
|
|
534
|
+
}
|
|
535
|
+
chunks.push({
|
|
536
|
+
id: event.id,
|
|
537
|
+
event,
|
|
538
|
+
start: currentStart,
|
|
539
|
+
end: event.end
|
|
540
|
+
});
|
|
541
|
+
if (chunks.length > 1) for (let i = 0; i < chunks.length; i++) chunks[i].id = (typeof event.id === "string" ? ":" : "") + `${event.id}#${i}`;
|
|
542
|
+
return chunks;
|
|
543
|
+
}
|
|
544
|
+
findUnitIndex(scale, event) {
|
|
545
|
+
const units = scale.units;
|
|
546
|
+
if (units.length === 0) return -1;
|
|
547
|
+
if (units.length === 1) return 0;
|
|
548
|
+
const pos = scale.eventToPosition(event);
|
|
549
|
+
if (pos.start < 0 && pos.end < 0) return -1;
|
|
550
|
+
for (let i = units.length - 1; i >= 0; i--) if (pos.start >= units[i].position - .001) return i;
|
|
551
|
+
return 0;
|
|
552
|
+
}
|
|
553
|
+
findUnitForPosition(scale, position) {
|
|
554
|
+
const units = scale.units;
|
|
555
|
+
for (let i = units.length - 1; i >= 0; i--) if (position >= units[i].position - .001) return i;
|
|
556
|
+
return 0;
|
|
557
|
+
}
|
|
558
|
+
mapToPrimitive(chunk, primaryUnit, secondaryScale, primaryAxis) {
|
|
559
|
+
const secPos = secondaryScale.eventToPosition({
|
|
560
|
+
...chunk.event,
|
|
561
|
+
start: chunk.start,
|
|
562
|
+
end: chunk.end
|
|
563
|
+
});
|
|
564
|
+
const s0 = Math.max(0, secPos.start);
|
|
565
|
+
const s1 = Math.min(100, secPos.end);
|
|
566
|
+
if (s1 <= s0) return null;
|
|
567
|
+
if (primaryAxis === "x") return {
|
|
568
|
+
id: chunk.id,
|
|
569
|
+
event: chunk.event,
|
|
570
|
+
x: primaryUnit.position,
|
|
571
|
+
width: primaryUnit.size,
|
|
572
|
+
y: s0,
|
|
573
|
+
height: s1 - s0
|
|
574
|
+
};
|
|
575
|
+
else return {
|
|
576
|
+
id: chunk.id,
|
|
577
|
+
event: chunk.event,
|
|
578
|
+
y: primaryUnit.position,
|
|
579
|
+
height: primaryUnit.size,
|
|
580
|
+
x: s0,
|
|
581
|
+
width: s1 - s0
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
const DAY_MS = 1440 * 60 * 1e3;
|
|
586
|
+
function deepMerge(target, source) {
|
|
587
|
+
const result = { ...target };
|
|
588
|
+
for (const key of Object.keys(source)) {
|
|
589
|
+
const sv = source[key];
|
|
590
|
+
const tv = result[key];
|
|
591
|
+
if (sv != null && typeof sv === "object" && !Array.isArray(sv) && typeof sv !== "function" && !(sv instanceof Date) && tv != null && typeof tv === "object" && !Array.isArray(tv) && typeof tv !== "function" && !(tv instanceof Date)) result[key] = deepMerge(tv, sv);
|
|
592
|
+
else result[key] = sv;
|
|
593
|
+
}
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/models/week_view.ts
|
|
599
|
+
var WeekViewModel = class extends ViewModel {
|
|
600
|
+
getSections() {
|
|
601
|
+
return [{
|
|
602
|
+
name: "multiday",
|
|
603
|
+
mode: "bars",
|
|
604
|
+
xScale: {
|
|
605
|
+
type: "date",
|
|
606
|
+
length: 7,
|
|
607
|
+
format: "weekScaleFormat"
|
|
608
|
+
},
|
|
609
|
+
yScale: {
|
|
610
|
+
type: "unit",
|
|
611
|
+
items: [{
|
|
612
|
+
id: "all",
|
|
613
|
+
label: ""
|
|
614
|
+
}],
|
|
615
|
+
accessor: "_",
|
|
616
|
+
visible: false
|
|
617
|
+
},
|
|
618
|
+
filter: (event) => isMultiDay(event),
|
|
619
|
+
size: "content-optional",
|
|
620
|
+
ui: { clipDrag: false }
|
|
621
|
+
}, {
|
|
622
|
+
name: "timeGrid",
|
|
623
|
+
mode: "boxes",
|
|
624
|
+
xScale: {
|
|
625
|
+
type: "date",
|
|
626
|
+
length: 7,
|
|
627
|
+
format: "weekScaleFormat"
|
|
628
|
+
},
|
|
629
|
+
yScale: {
|
|
630
|
+
type: "time",
|
|
631
|
+
startHour: 8,
|
|
632
|
+
endHour: 18,
|
|
633
|
+
step: 60,
|
|
634
|
+
snapStep: 15,
|
|
635
|
+
ui: { minUnitHeight: 100 },
|
|
636
|
+
format: "timeScaleFormat"
|
|
637
|
+
},
|
|
638
|
+
filter: (event) => !isMultiDay(event),
|
|
639
|
+
size: 1
|
|
640
|
+
}];
|
|
641
|
+
}
|
|
642
|
+
getRangeLabel() {
|
|
643
|
+
const start = this.startDate;
|
|
644
|
+
const end = /* @__PURE__ */ new Date(this.endDate.getTime() - 1);
|
|
645
|
+
return `${this.fmt("titleWeekFormatStart")(start)}–${this.fmt("titleWeekFormatEnd")(end)}`;
|
|
646
|
+
}
|
|
647
|
+
rangeStart(date) {
|
|
648
|
+
const d = new Date(date);
|
|
649
|
+
d.setHours(0, 0, 0, 0);
|
|
650
|
+
const diff = ((d.getDay() - this.weekStartDay) % 7 + 7) % 7;
|
|
651
|
+
d.setDate(d.getDate() - diff);
|
|
652
|
+
return d;
|
|
653
|
+
}
|
|
654
|
+
addRange(date, n) {
|
|
655
|
+
const d = new Date(date);
|
|
656
|
+
d.setDate(d.getDate() + n * 7);
|
|
657
|
+
return d;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region src/models/day_view.ts
|
|
663
|
+
var DayViewModel = class extends ViewModel {
|
|
664
|
+
getSections() {
|
|
665
|
+
return [{
|
|
666
|
+
name: "multiday",
|
|
667
|
+
mode: "bars",
|
|
668
|
+
xScale: {
|
|
669
|
+
type: "date",
|
|
670
|
+
length: 1,
|
|
671
|
+
visible: false
|
|
672
|
+
},
|
|
673
|
+
yScale: {
|
|
674
|
+
type: "unit",
|
|
675
|
+
items: [{
|
|
676
|
+
id: "all",
|
|
677
|
+
label: ""
|
|
678
|
+
}],
|
|
679
|
+
accessor: "_",
|
|
680
|
+
visible: false
|
|
681
|
+
},
|
|
682
|
+
filter: (event) => isMultiDay(event),
|
|
683
|
+
size: "content-optional",
|
|
684
|
+
ui: {
|
|
685
|
+
drag: false,
|
|
686
|
+
dragCreate: false
|
|
687
|
+
}
|
|
688
|
+
}, {
|
|
689
|
+
name: "timeGrid",
|
|
690
|
+
mode: "boxes",
|
|
691
|
+
xScale: {
|
|
692
|
+
type: "date",
|
|
693
|
+
length: 1,
|
|
694
|
+
visible: false
|
|
695
|
+
},
|
|
696
|
+
yScale: {
|
|
697
|
+
type: "time",
|
|
698
|
+
startHour: 8,
|
|
699
|
+
endHour: 18,
|
|
700
|
+
step: 60,
|
|
701
|
+
snapStep: 15,
|
|
702
|
+
ui: { minUnitHeight: 100 },
|
|
703
|
+
format: "timeScaleFormat"
|
|
704
|
+
},
|
|
705
|
+
filter: (event) => !isMultiDay(event),
|
|
706
|
+
size: 1
|
|
707
|
+
}];
|
|
708
|
+
}
|
|
709
|
+
getRangeLabel() {
|
|
710
|
+
return this.fmt("titleDayFormat")(this.startDate);
|
|
711
|
+
}
|
|
712
|
+
rangeStart(date) {
|
|
713
|
+
const d = new Date(date);
|
|
714
|
+
d.setHours(0, 0, 0, 0);
|
|
715
|
+
return d;
|
|
716
|
+
}
|
|
717
|
+
addRange(date, n) {
|
|
718
|
+
const d = new Date(date);
|
|
719
|
+
d.setDate(d.getDate() + n);
|
|
720
|
+
return d;
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
//#endregion
|
|
725
|
+
//#region src/models/month_view.ts
|
|
726
|
+
var MonthViewModel = class extends ViewModel {
|
|
727
|
+
snapToCell = false;
|
|
728
|
+
getSections() {
|
|
729
|
+
return [{
|
|
730
|
+
name: "month",
|
|
731
|
+
mode: "grid",
|
|
732
|
+
xScale: {
|
|
733
|
+
type: "date",
|
|
734
|
+
length: 7,
|
|
735
|
+
discrete: true,
|
|
736
|
+
format: "monthScaleFormat"
|
|
737
|
+
},
|
|
738
|
+
yScale: {
|
|
739
|
+
type: "date",
|
|
740
|
+
length: this.getWeekCount(),
|
|
741
|
+
step: 7,
|
|
742
|
+
discrete: true,
|
|
743
|
+
visible: false
|
|
744
|
+
},
|
|
745
|
+
size: 1,
|
|
746
|
+
ui: { clipDrag: false }
|
|
747
|
+
}];
|
|
748
|
+
}
|
|
749
|
+
getRangeLabel() {
|
|
750
|
+
const target = this.getTargetMonth(this.startDate);
|
|
751
|
+
return this.fmt("titleMonthFormat")(target);
|
|
752
|
+
}
|
|
753
|
+
setRange(date) {
|
|
754
|
+
this.startDate = this.rangeStart(date);
|
|
755
|
+
const weekCount = this.getWeekCount();
|
|
756
|
+
this.endDate = new Date(this.startDate);
|
|
757
|
+
this.endDate.setDate(this.endDate.getDate() + weekCount * 7);
|
|
758
|
+
return [this.startDate, this.endDate];
|
|
759
|
+
}
|
|
760
|
+
rangeStart(date) {
|
|
761
|
+
const first = new Date(date);
|
|
762
|
+
first.setDate(1);
|
|
763
|
+
first.setHours(0, 0, 0, 0);
|
|
764
|
+
const diff = ((first.getDay() - this.weekStartDay) % 7 + 7) % 7;
|
|
765
|
+
first.setDate(first.getDate() - diff);
|
|
766
|
+
return first;
|
|
767
|
+
}
|
|
768
|
+
addRange(date, n) {
|
|
769
|
+
const d = new Date(date);
|
|
770
|
+
d.setDate(1);
|
|
771
|
+
d.setMonth(d.getMonth() + n);
|
|
772
|
+
return d;
|
|
773
|
+
}
|
|
774
|
+
sortBeforeLayout(primitives, _mode) {
|
|
775
|
+
primitives.sort((a, b) => {
|
|
776
|
+
const dx = a.x - b.x;
|
|
777
|
+
if (dx !== 0) return dx;
|
|
778
|
+
return (a.isMultiDay ? 0 : 1) - (b.isMultiDay ? 0 : 1);
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
mapToPrimitive(chunk, primaryUnit, secondaryScale, primaryAxis) {
|
|
782
|
+
const prim = super.mapToPrimitive(chunk, primaryUnit, secondaryScale, primaryAxis);
|
|
783
|
+
if (!prim) return null;
|
|
784
|
+
const isMultiDay = chunk.event.allDay || !isSameDay(chunk.event.start, chunk.event.end);
|
|
785
|
+
prim.isMultiDay = isMultiDay;
|
|
786
|
+
if (!isMultiDay || this.snapToCell) {
|
|
787
|
+
const startUnitIdx = this.findUnitIndex(secondaryScale, {
|
|
788
|
+
...chunk.event,
|
|
789
|
+
start: chunk.start,
|
|
790
|
+
end: chunk.end
|
|
791
|
+
});
|
|
792
|
+
if (startUnitIdx === -1) return null;
|
|
793
|
+
const startUnit = secondaryScale.units[startUnitIdx];
|
|
794
|
+
let endUnit = startUnit;
|
|
795
|
+
if (isMultiDay) {
|
|
796
|
+
const endDate = /* @__PURE__ */ new Date(chunk.end.getTime() - 1);
|
|
797
|
+
const endUnitIdx = this.findUnitIndex(secondaryScale, {
|
|
798
|
+
...chunk.event,
|
|
799
|
+
start: endDate,
|
|
800
|
+
end: endDate
|
|
801
|
+
});
|
|
802
|
+
if (endUnitIdx !== -1) endUnit = secondaryScale.units[endUnitIdx];
|
|
803
|
+
}
|
|
804
|
+
if (primaryAxis === "x") {
|
|
805
|
+
prim.y = startUnit.position;
|
|
806
|
+
prim.height = endUnit.position + endUnit.size - startUnit.position;
|
|
807
|
+
} else {
|
|
808
|
+
prim.x = startUnit.position;
|
|
809
|
+
prim.width = endUnit.position + endUnit.size - startUnit.position;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return prim;
|
|
813
|
+
}
|
|
814
|
+
buildCells(xScale, yScale) {
|
|
815
|
+
const targetMonth = this.getTargetMonth(this.startDate).getMonth();
|
|
816
|
+
const today = /* @__PURE__ */ new Date();
|
|
817
|
+
today.setHours(0, 0, 0, 0);
|
|
818
|
+
const todayTime = today.getTime();
|
|
819
|
+
const cells = [];
|
|
820
|
+
const xUnits = xScale.units;
|
|
821
|
+
const yUnits = yScale.units;
|
|
822
|
+
for (let row = 0; row < yUnits.length; row++) for (let col = 0; col < xUnits.length; col++) {
|
|
823
|
+
const date = new Date(this.startDate);
|
|
824
|
+
date.setDate(date.getDate() + row * 7 + col);
|
|
825
|
+
date.setHours(0, 0, 0, 0);
|
|
826
|
+
const dow = date.getDay();
|
|
827
|
+
cells.push({
|
|
828
|
+
date,
|
|
829
|
+
day: date.getDate(),
|
|
830
|
+
inMonth: date.getMonth() === targetMonth,
|
|
831
|
+
today: date.getTime() === todayTime,
|
|
832
|
+
weekend: dow === 0 || dow === 6,
|
|
833
|
+
x: xUnits[col].position,
|
|
834
|
+
y: yUnits[row].position,
|
|
835
|
+
width: xUnits[col].size,
|
|
836
|
+
height: yUnits[row].size
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
return cells;
|
|
840
|
+
}
|
|
841
|
+
getTargetMonth(date) {
|
|
842
|
+
const d = new Date(date);
|
|
843
|
+
d.setDate(d.getDate() + 7);
|
|
844
|
+
return new Date(d.getFullYear(), d.getMonth(), 1);
|
|
845
|
+
}
|
|
846
|
+
getWeekCount() {
|
|
847
|
+
const target = this.getTargetMonth(this.startDate);
|
|
848
|
+
const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0);
|
|
849
|
+
lastDay.setHours(0, 0, 0, 0);
|
|
850
|
+
const dow = lastDay.getDay();
|
|
851
|
+
const daysUntilNextStart = ((this.weekStartDay - dow) % 7 + 7) % 7 || 7;
|
|
852
|
+
const gridEnd = new Date(lastDay);
|
|
853
|
+
gridEnd.setDate(gridEnd.getDate() + daysUntilNextStart);
|
|
854
|
+
const diffMs = gridEnd.getTime() - this.startDate.getTime();
|
|
855
|
+
return Math.round(diffMs / (1440 * 60 * 1e3)) / 7;
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
function isSameDay(a, b) {
|
|
859
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
//#endregion
|
|
863
|
+
//#region src/constants.ts
|
|
864
|
+
function getMenuOptions() {
|
|
865
|
+
return [{
|
|
866
|
+
id: "edit-event",
|
|
867
|
+
text: "Edit event",
|
|
868
|
+
icon: "wxi-edit"
|
|
869
|
+
}, {
|
|
870
|
+
id: "delete-event",
|
|
871
|
+
text: "Delete event",
|
|
872
|
+
icon: "wxi-delete"
|
|
873
|
+
}];
|
|
874
|
+
}
|
|
875
|
+
function getToolbarItems() {
|
|
876
|
+
return [
|
|
877
|
+
{
|
|
878
|
+
id: "nav",
|
|
879
|
+
comp: "dateNav"
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
id: "today",
|
|
883
|
+
comp: "todayButton"
|
|
884
|
+
},
|
|
885
|
+
{ comp: "spacer" },
|
|
886
|
+
{
|
|
887
|
+
id: "title",
|
|
888
|
+
comp: "dateLabel"
|
|
889
|
+
},
|
|
890
|
+
{ comp: "spacer" },
|
|
891
|
+
{
|
|
892
|
+
id: "modes",
|
|
893
|
+
comp: "richselect"
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
id: "add-event",
|
|
897
|
+
comp: "addEventButton"
|
|
898
|
+
}
|
|
899
|
+
];
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
//#endregion
|
|
903
|
+
//#region src/registry.ts
|
|
904
|
+
const registry = /* @__PURE__ */ new Map();
|
|
905
|
+
function registerCalendarView(id, viewClass) {
|
|
906
|
+
registry.set(id, viewClass);
|
|
907
|
+
}
|
|
908
|
+
function getRegisteredViews() {
|
|
909
|
+
return registry;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
//#endregion
|
|
913
|
+
//#region src/actions/navigate-to.ts
|
|
914
|
+
function navigateTo(store, action) {
|
|
915
|
+
const updates = {};
|
|
916
|
+
if (action.date) updates.currentDate = action.date;
|
|
917
|
+
if (action.view && store.getView(action.view)) updates.currentView = action.view;
|
|
918
|
+
if (Object.keys(updates).length) store.setState(updates);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
//#endregion
|
|
922
|
+
//#region src/actions/navigate-time.ts
|
|
923
|
+
function navigateTime(store, bus, action) {
|
|
924
|
+
const { _view, currentDate } = store.getState();
|
|
925
|
+
let date;
|
|
926
|
+
switch (action.direction) {
|
|
927
|
+
case "next":
|
|
928
|
+
date = _view.addRange(currentDate, 1);
|
|
929
|
+
break;
|
|
930
|
+
case "previous":
|
|
931
|
+
date = _view.addRange(currentDate, -1);
|
|
932
|
+
break;
|
|
933
|
+
case "now":
|
|
934
|
+
date = /* @__PURE__ */ new Date();
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
bus.exec("navigate-to", { date });
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
//#endregion
|
|
941
|
+
//#region src/actions/filter-events.ts
|
|
942
|
+
function filterEvents(store, action) {
|
|
943
|
+
const { filter, tag } = action;
|
|
944
|
+
const prev = store.getState().filters;
|
|
945
|
+
if (!filter) {
|
|
946
|
+
if (!tag) store.setState({ filters: /* @__PURE__ */ new Map() });
|
|
947
|
+
else if (prev.has(tag)) {
|
|
948
|
+
const next = new Map(prev);
|
|
949
|
+
next.delete(tag);
|
|
950
|
+
store.setState({ filters: next });
|
|
951
|
+
}
|
|
952
|
+
} else {
|
|
953
|
+
const next = new Map(prev);
|
|
954
|
+
next.set(tag || "_default", filter);
|
|
955
|
+
store.setState({ filters: next });
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
//#endregion
|
|
960
|
+
//#region src/helpers/allDay.ts
|
|
961
|
+
function startOfLocalDay(date) {
|
|
962
|
+
const d = new Date(date);
|
|
963
|
+
d.setHours(0, 0, 0, 0);
|
|
964
|
+
return d;
|
|
965
|
+
}
|
|
966
|
+
function nextLocalDay(date) {
|
|
967
|
+
const d = startOfLocalDay(date);
|
|
968
|
+
d.setDate(d.getDate() + 1);
|
|
969
|
+
return d;
|
|
970
|
+
}
|
|
971
|
+
function isLocalMidnight(date) {
|
|
972
|
+
return date.getHours() === 0 && date.getMinutes() === 0 && date.getSeconds() === 0 && date.getMilliseconds() === 0;
|
|
973
|
+
}
|
|
974
|
+
function isSameLocalDay(a, b) {
|
|
975
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
976
|
+
}
|
|
977
|
+
function normalizeAllDayEnd(start, end) {
|
|
978
|
+
const startDay = startOfLocalDay(start);
|
|
979
|
+
if (!(end instanceof Date)) return nextLocalDay(startDay);
|
|
980
|
+
if (end <= startDay || isSameLocalDay(startDay, end)) return nextLocalDay(startDay);
|
|
981
|
+
if (isLocalMidnight(end)) return new Date(end);
|
|
982
|
+
return nextLocalDay(end);
|
|
983
|
+
}
|
|
984
|
+
function normalizeAllDayEvent(event) {
|
|
985
|
+
if (event.allDay !== true || !(event.start instanceof Date)) return event;
|
|
986
|
+
const start = startOfLocalDay(event.start);
|
|
987
|
+
const end = normalizeAllDayEnd(start, event.end);
|
|
988
|
+
return {
|
|
989
|
+
...event,
|
|
990
|
+
start,
|
|
991
|
+
end
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
function normalizeAllDayUpdate(updates, existing) {
|
|
995
|
+
const merged = existing ? {
|
|
996
|
+
...existing,
|
|
997
|
+
...updates
|
|
998
|
+
} : updates;
|
|
999
|
+
if (merged.allDay !== true || !(merged.start instanceof Date)) return updates;
|
|
1000
|
+
const normalized = normalizeAllDayEvent(merged);
|
|
1001
|
+
return {
|
|
1002
|
+
...updates,
|
|
1003
|
+
start: normalized.start,
|
|
1004
|
+
end: normalized.end
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
//#endregion
|
|
1009
|
+
//#region src/actions/update-event.ts
|
|
1010
|
+
function updateEvent(store, action) {
|
|
1011
|
+
const { events } = store.getState();
|
|
1012
|
+
const event = normalizeAllDayUpdate(action.event, events.getEvent(action.id));
|
|
1013
|
+
action.event = event;
|
|
1014
|
+
if (events.updateEvent(action.id, event, action.mode, action.originalDate)) store.setState({ events });
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
//#endregion
|
|
1018
|
+
//#region src/actions/add-event.ts
|
|
1019
|
+
function hasTimeScale(store) {
|
|
1020
|
+
const { _view } = store.getState();
|
|
1021
|
+
return _view.getSections().some((s) => s.xScale.type === "time" || s.yScale.type === "time");
|
|
1022
|
+
}
|
|
1023
|
+
function roundToNextHour(date) {
|
|
1024
|
+
const d = new Date(date);
|
|
1025
|
+
if (d.getMinutes() > 0 || d.getSeconds() > 0 || d.getMilliseconds() > 0) d.setHours(d.getHours() + 1, 0, 0, 0);
|
|
1026
|
+
return d;
|
|
1027
|
+
}
|
|
1028
|
+
function applyDefaults(event, store) {
|
|
1029
|
+
if (event.start && event.end) return event;
|
|
1030
|
+
const { currentDate } = store.getState();
|
|
1031
|
+
if (hasTimeScale(store)) {
|
|
1032
|
+
const start = event.start ?? roundToNextHour(/* @__PURE__ */ new Date());
|
|
1033
|
+
const end = event.end ?? new Date(new Date(start).getTime() + 3600 * 1e3);
|
|
1034
|
+
return {
|
|
1035
|
+
...event,
|
|
1036
|
+
start,
|
|
1037
|
+
end
|
|
1038
|
+
};
|
|
1039
|
+
} else {
|
|
1040
|
+
const start = event.start ?? new Date(new Date(currentDate).setHours(0, 0, 0, 0));
|
|
1041
|
+
const end = event.end ?? new Date(new Date(start).getTime() + 1440 * 60 * 1e3);
|
|
1042
|
+
return {
|
|
1043
|
+
...event,
|
|
1044
|
+
allDay: event.allDay ?? true,
|
|
1045
|
+
start,
|
|
1046
|
+
end
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
function addEvent(store, action) {
|
|
1051
|
+
const filled = normalizeAllDayEvent(applyDefaults(action.event, store));
|
|
1052
|
+
const { events } = store.getState();
|
|
1053
|
+
const full = events.addEvent(filled);
|
|
1054
|
+
action.event = { ...full };
|
|
1055
|
+
action.id = full.id;
|
|
1056
|
+
const updates = { events };
|
|
1057
|
+
if (action.edit) updates.editorData = { ...full };
|
|
1058
|
+
store.setState(updates);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
//#endregion
|
|
1062
|
+
//#region src/actions/select-event.ts
|
|
1063
|
+
function selectEvent(store, params) {
|
|
1064
|
+
if (params.id == null) {
|
|
1065
|
+
store.setState({ editorData: null });
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
const { events } = store.getState();
|
|
1069
|
+
const event = events.getEvent(params.id);
|
|
1070
|
+
if (event) store.setState({ editorData: { ...event } });
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
//#endregion
|
|
1074
|
+
//#region src/actions/delete-event.ts
|
|
1075
|
+
function deleteEvent(store, params) {
|
|
1076
|
+
const { events, editorData } = store.getState();
|
|
1077
|
+
events.removeEvent(params.id);
|
|
1078
|
+
const updates = { events };
|
|
1079
|
+
if (editorData?.id === params.id) updates.editorData = null;
|
|
1080
|
+
store.setState(updates);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
//#endregion
|
|
1084
|
+
//#region src/actions/index.ts
|
|
1085
|
+
function init(inBus, store) {
|
|
1086
|
+
inBus.on("navigate-to", (params) => navigateTo(store, params));
|
|
1087
|
+
inBus.on("navigate-time", (params) => navigateTime(store, inBus, params));
|
|
1088
|
+
inBus.on("filter-events", (params) => filterEvents(store, params));
|
|
1089
|
+
inBus.on("update-event", (params) => updateEvent(store, params));
|
|
1090
|
+
inBus.on("add-event", (params) => addEvent(store, params));
|
|
1091
|
+
inBus.on("select-event", (params) => selectEvent(store, params));
|
|
1092
|
+
inBus.on("delete-event", (params) => deleteEvent(store, params));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
//#endregion
|
|
1096
|
+
//#region src/calendar_reactive.ts
|
|
1097
|
+
function reactive(store) {
|
|
1098
|
+
return [
|
|
1099
|
+
{
|
|
1100
|
+
in: ["currentDate", "currentView"],
|
|
1101
|
+
out: ["rangeLabel", "visibleDateRange"],
|
|
1102
|
+
exec: (ctx) => {
|
|
1103
|
+
const { currentView, currentDate } = store.getState();
|
|
1104
|
+
const view = store.getView(currentView);
|
|
1105
|
+
const [start, end] = view.setRange(currentDate);
|
|
1106
|
+
store.setState({
|
|
1107
|
+
rangeLabel: view.getRangeLabel(),
|
|
1108
|
+
visibleDateRange: {
|
|
1109
|
+
start,
|
|
1110
|
+
end
|
|
1111
|
+
}
|
|
1112
|
+
}, ctx);
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
in: ["currentView"],
|
|
1117
|
+
out: ["_view"],
|
|
1118
|
+
exec: (ctx) => {
|
|
1119
|
+
const { currentView } = store.getState();
|
|
1120
|
+
const view = store.getView(currentView);
|
|
1121
|
+
store.setState({ _view: view }, ctx);
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
in: [
|
|
1126
|
+
"events",
|
|
1127
|
+
"currentDate",
|
|
1128
|
+
"currentView",
|
|
1129
|
+
"_view",
|
|
1130
|
+
"filters"
|
|
1131
|
+
],
|
|
1132
|
+
out: ["viewData"],
|
|
1133
|
+
exec: (ctx) => {
|
|
1134
|
+
const { _view, currentDate, events, filters } = store.getState();
|
|
1135
|
+
const [startDate, endDate] = _view.setRange(currentDate);
|
|
1136
|
+
let evs = events.getEvents(startDate, endDate);
|
|
1137
|
+
for (const fn of filters.values()) evs = evs.filter(fn);
|
|
1138
|
+
const viewData = _view.process(evs);
|
|
1139
|
+
store.setState({ viewData }, ctx);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
];
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
//#endregion
|
|
1146
|
+
//#region src/calendar_store.ts
|
|
1147
|
+
registerCalendarView("week", WeekViewModel);
|
|
1148
|
+
registerCalendarView("day", DayViewModel);
|
|
1149
|
+
registerCalendarView("month", MonthViewModel);
|
|
1150
|
+
var CalendarStore = class extends Store {
|
|
1151
|
+
in;
|
|
1152
|
+
_router;
|
|
1153
|
+
_views = {};
|
|
1154
|
+
_weekStartDay;
|
|
1155
|
+
_dateFormat;
|
|
1156
|
+
constructor(w, options) {
|
|
1157
|
+
options?.recurring;
|
|
1158
|
+
let EventsClass = EventsStore;
|
|
1159
|
+
const defaultState = {
|
|
1160
|
+
currentDate: /* @__PURE__ */ new Date(),
|
|
1161
|
+
currentView: "",
|
|
1162
|
+
rangeLabel: "",
|
|
1163
|
+
visibleDateRange: {
|
|
1164
|
+
start: /* @__PURE__ */ new Date(),
|
|
1165
|
+
end: /* @__PURE__ */ new Date()
|
|
1166
|
+
},
|
|
1167
|
+
events: new EventsClass(),
|
|
1168
|
+
viewData: {},
|
|
1169
|
+
filters: /* @__PURE__ */ new Map(),
|
|
1170
|
+
editorData: null,
|
|
1171
|
+
_view: new WeekViewModel()
|
|
1172
|
+
};
|
|
1173
|
+
super({
|
|
1174
|
+
writable: w,
|
|
1175
|
+
async: false
|
|
1176
|
+
});
|
|
1177
|
+
this._weekStartDay = options?.weekStart ?? 1;
|
|
1178
|
+
this._dateFormat = options?.dateFormat ?? (() => (d) => d.toLocaleDateString());
|
|
1179
|
+
this.configureViews();
|
|
1180
|
+
super.setState(defaultState);
|
|
1181
|
+
this._router = new DataRouter(super.setState.bind(this), reactive(this), { events: (v) => new EventsClass(v) });
|
|
1182
|
+
init(this.in = new EventBus(), this);
|
|
1183
|
+
}
|
|
1184
|
+
configureViews(views) {
|
|
1185
|
+
const registered = getRegisteredViews();
|
|
1186
|
+
this._views = {};
|
|
1187
|
+
if (!views) for (const [id, ViewClass] of registered) {
|
|
1188
|
+
const instance = new ViewClass();
|
|
1189
|
+
this.applyViewConfig(instance);
|
|
1190
|
+
this._views[id] = instance;
|
|
1191
|
+
}
|
|
1192
|
+
else {
|
|
1193
|
+
for (const v of views) {
|
|
1194
|
+
const id = typeof v === "string" ? v : v.id;
|
|
1195
|
+
const ViewClass = registered.get(id);
|
|
1196
|
+
if (!ViewClass) continue;
|
|
1197
|
+
const instance = new ViewClass();
|
|
1198
|
+
this.applyViewConfig(instance);
|
|
1199
|
+
if (typeof v !== "string" && v.sections) instance.configure(v.sections);
|
|
1200
|
+
this._views[id] = instance;
|
|
1201
|
+
}
|
|
1202
|
+
const cv = this.getState().currentView;
|
|
1203
|
+
if (cv && this._views[cv]) this.setState({ _view: this._views[cv] });
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
init(state) {
|
|
1207
|
+
this._router.init({ ...state });
|
|
1208
|
+
}
|
|
1209
|
+
applyViewConfig(instance) {
|
|
1210
|
+
instance.weekStartDay = this._weekStartDay;
|
|
1211
|
+
instance.fmt = this._dateFormat;
|
|
1212
|
+
}
|
|
1213
|
+
getView(name) {
|
|
1214
|
+
return this._views[name];
|
|
1215
|
+
}
|
|
1216
|
+
getEvents(start, end) {
|
|
1217
|
+
return this.getState().events.getEvents(start, end);
|
|
1218
|
+
}
|
|
1219
|
+
getEvent(id) {
|
|
1220
|
+
if (typeof id === "string") {
|
|
1221
|
+
const isString = id.startsWith(":");
|
|
1222
|
+
const start = isString ? 1 : 0;
|
|
1223
|
+
let idx = id.indexOf("#", start);
|
|
1224
|
+
if (idx === -1 && isString) idx = id.length;
|
|
1225
|
+
if (idx !== -1) {
|
|
1226
|
+
id = id.substring(start, idx);
|
|
1227
|
+
if (!isString) id = parseInt(id);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return this.getState().events.getEvent(id);
|
|
1231
|
+
}
|
|
1232
|
+
getBrandmark() {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
setState(state, ctx) {
|
|
1236
|
+
return this._router.setState(state, ctx);
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
//#endregion
|
|
1241
|
+
//#region src/index.ts
|
|
1242
|
+
const version = version$1;
|
|
1243
|
+
|
|
1244
|
+
//#endregion
|
|
1245
|
+
export { CalendarStore, DayViewModel, DiscreteScale, EventsStore, LinearScale, MonthViewModel, ViewModel, WeekViewModel, createScale, getMenuOptions, getToolbarItems, isMultiDay, layoutBars, layoutBoxes, registerCalendarView, version };
|