@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.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 };