@tasknotes/calendar 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +163 -2
  4. package/dist/calendar.css +1358 -0
  5. package/dist/cjs/core.cjs +716 -0
  6. package/dist/cjs/index.cjs +4240 -0
  7. package/dist/cjs/react.cjs +4068 -0
  8. package/dist/esm/chunk-6RRDNLZM.js +3435 -0
  9. package/dist/esm/chunk-DVMZ5OXN.js +694 -0
  10. package/dist/esm/chunk-L4WKZ6WT.js +0 -0
  11. package/dist/esm/core.js +85 -0
  12. package/dist/esm/index.js +223 -0
  13. package/dist/esm/react.js +46 -0
  14. package/dist/types/core/date.d.cts +30 -0
  15. package/dist/types/core/date.d.ts +30 -0
  16. package/dist/types/core/index.d.cts +7 -0
  17. package/dist/types/core/index.d.ts +7 -0
  18. package/dist/types/core/layout.d.cts +82 -0
  19. package/dist/types/core/layout.d.ts +82 -0
  20. package/dist/types/core/order.d.cts +7 -0
  21. package/dist/types/core/order.d.ts +7 -0
  22. package/dist/types/core/plan.d.cts +42 -0
  23. package/dist/types/core/plan.d.ts +42 -0
  24. package/dist/types/core/store.d.cts +60 -0
  25. package/dist/types/core/store.d.ts +60 -0
  26. package/dist/types/core/types.d.cts +69 -0
  27. package/dist/types/core/types.d.ts +69 -0
  28. package/dist/types/core/zone.d.cts +43 -0
  29. package/dist/types/core/zone.d.ts +43 -0
  30. package/dist/types/dom/calendar.d.cts +215 -0
  31. package/dist/types/dom/calendar.d.ts +215 -0
  32. package/dist/types/dom/context.d.cts +107 -0
  33. package/dist/types/dom/context.d.ts +107 -0
  34. package/dist/types/dom/dom.d.cts +8 -0
  35. package/dist/types/dom/dom.d.ts +8 -0
  36. package/dist/types/dom/format.d.cts +30 -0
  37. package/dist/types/dom/format.d.ts +30 -0
  38. package/dist/types/dom/options.d.cts +401 -0
  39. package/dist/types/dom/options.d.ts +401 -0
  40. package/dist/types/dom/views/daygrid.d.cts +42 -0
  41. package/dist/types/dom/views/daygrid.d.ts +42 -0
  42. package/dist/types/dom/views/list.d.cts +27 -0
  43. package/dist/types/dom/views/list.d.ts +27 -0
  44. package/dist/types/dom/views/timegrid.d.cts +54 -0
  45. package/dist/types/dom/views/timegrid.d.ts +54 -0
  46. package/dist/types/dom/views/timeline.d.cts +83 -0
  47. package/dist/types/dom/views/timeline.d.ts +83 -0
  48. package/dist/types/dom/views/year.d.cts +26 -0
  49. package/dist/types/dom/views/year.d.ts +26 -0
  50. package/dist/types/index.d.cts +6 -0
  51. package/dist/types/index.d.ts +6 -0
  52. package/dist/types/react/index.d.cts +20 -0
  53. package/dist/types/react/index.d.ts +20 -0
  54. package/dist/types/tasknotes/bases.d.cts +50 -0
  55. package/dist/types/tasknotes/bases.d.ts +50 -0
  56. package/package.json +108 -5
@@ -0,0 +1,694 @@
1
+ // src/core/date.ts
2
+ var DAY_MS = 864e5;
3
+ var MINUTES_PER_DAY = 1440;
4
+ function dayFromYMD(year, month, day) {
5
+ const d = /* @__PURE__ */ new Date(0);
6
+ d.setUTCFullYear(year, month - 1, day);
7
+ d.setUTCHours(0, 0, 0, 0);
8
+ return Math.floor(d.getTime() / DAY_MS);
9
+ }
10
+ function ymdOfDay(day) {
11
+ const d = new Date(day * DAY_MS);
12
+ return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1, day: d.getUTCDate() };
13
+ }
14
+ function weekdayOf(day) {
15
+ return ((day + 4) % 7 + 7) % 7;
16
+ }
17
+ function startOfWeek(day, firstDay) {
18
+ return day - (weekdayOf(day) - firstDay + 7) % 7;
19
+ }
20
+ function startOfMonth(day) {
21
+ const { year, month } = ymdOfDay(day);
22
+ return dayFromYMD(year, month, 1);
23
+ }
24
+ function daysInMonth(year, month) {
25
+ return dayFromYMD(year, month + 1, 1) - dayFromYMD(year, month, 1);
26
+ }
27
+ function addMonths(day, months) {
28
+ const { year, month, day: dom } = ymdOfDay(day);
29
+ const total = year * 12 + (month - 1) + months;
30
+ const y = Math.floor(total / 12);
31
+ const m = total % 12 + 1;
32
+ return dayFromYMD(y, m, Math.min(dom, daysInMonth(y, m)));
33
+ }
34
+ function startOfYear(day) {
35
+ return dayFromYMD(ymdOfDay(day).year, 1, 1);
36
+ }
37
+ var pad2 = (n) => String(n).padStart(2, "0");
38
+ function formatDay(day) {
39
+ const { year, month, day: d } = ymdOfDay(day);
40
+ const y = year < 0 ? "-" + String(-year).padStart(6, "0") : String(year).padStart(4, "0");
41
+ return `${y}-${pad2(month)}-${pad2(d)}`;
42
+ }
43
+ var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
44
+ function parseDay(value) {
45
+ const m = DATE_RE.exec(value.trim());
46
+ if (!m) return null;
47
+ const year = Number(m[1]);
48
+ const month = Number(m[2]);
49
+ const d = Number(m[3]);
50
+ if (month < 1 || month > 12 || d < 1 || d > daysInMonth(year, month)) return null;
51
+ return dayFromYMD(year, month, d);
52
+ }
53
+ function isoWeekNumber(day) {
54
+ const thursday = day - (weekdayOf(day) + 6) % 7 + 3;
55
+ const jan1 = dayFromYMD(ymdOfDay(thursday).year, 1, 1);
56
+ return Math.floor((thursday - jan1) / 7) + 1;
57
+ }
58
+ function parseDurationMinutes(value, fallback) {
59
+ if (typeof value === "number" && Number.isFinite(value)) return value;
60
+ if (typeof value !== "string") return fallback;
61
+ const m = /^(-?\d{1,3}):(\d{2})(?::(\d{2}))?$/.exec(value.trim());
62
+ if (!m) return fallback;
63
+ const sign = m[1].startsWith("-") ? -1 : 1;
64
+ return sign * (Math.abs(Number(m[1])) * 60 + Number(m[2]) + Number(m[3] ?? 0) / 60);
65
+ }
66
+ function formatMinutes(minutes) {
67
+ const m = (Math.round(minutes) % MINUTES_PER_DAY + MINUTES_PER_DAY) % MINUTES_PER_DAY;
68
+ return `${pad2(Math.floor(m / 60))}:${pad2(m % 60)}`;
69
+ }
70
+
71
+ // src/core/zone.ts
72
+ var LocalZone = class {
73
+ id = "local";
74
+ offsetAt(ms) {
75
+ return -new Date(ms).getTimezoneOffset();
76
+ }
77
+ };
78
+ var IanaZone = class {
79
+ constructor(id) {
80
+ this.id = id;
81
+ this.fmt = new Intl.DateTimeFormat("en-US", {
82
+ timeZone: id,
83
+ hourCycle: "h23",
84
+ year: "numeric",
85
+ month: "numeric",
86
+ day: "numeric",
87
+ hour: "numeric",
88
+ minute: "numeric",
89
+ second: "numeric"
90
+ });
91
+ }
92
+ id;
93
+ fmt;
94
+ cache = /* @__PURE__ */ new Map();
95
+ offsetAt(ms) {
96
+ const bucket = Math.floor(ms / 9e5);
97
+ const hit = this.cache.get(bucket);
98
+ if (hit !== void 0) return hit;
99
+ const at = bucket * 9e5;
100
+ const p = {};
101
+ for (const part of this.fmt.formatToParts(new Date(at))) {
102
+ if (part.type !== "literal") p[part.type] = Number(part.value);
103
+ }
104
+ const wall = dayFromYMD(p.year, p.month, p.day) * DAY_MS + ((p.hour * 60 + p.minute) * 60 + p.second) * 1e3;
105
+ const offset = Math.round((wall - at) / 6e4);
106
+ if (this.cache.size > 5e3) this.cache.clear();
107
+ this.cache.set(bucket, offset);
108
+ return offset;
109
+ }
110
+ };
111
+ var zones = /* @__PURE__ */ new Map();
112
+ function getTimeZone(id) {
113
+ const key = !id || id === "local" ? "local" : id;
114
+ let z = zones.get(key);
115
+ if (!z) {
116
+ z = key === "local" ? new LocalZone() : new IanaZone(key);
117
+ zones.set(key, z);
118
+ }
119
+ return z;
120
+ }
121
+ function isValidTimeZone(id) {
122
+ if (id === "local") return true;
123
+ try {
124
+ new Intl.DateTimeFormat("en-US", { timeZone: id });
125
+ return true;
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+ function wallTime(ms, zone) {
131
+ const local = ms + zone.offsetAt(ms) * 6e4;
132
+ const day = Math.floor(local / DAY_MS);
133
+ return { day, minute: Math.floor((local - day * DAY_MS) / 6e4) };
134
+ }
135
+ function dayOf(ms, zone) {
136
+ return wallTime(ms, zone).day;
137
+ }
138
+ function toInstant(day, minute, zone) {
139
+ const localTs = day * DAY_MS + minute * 6e4;
140
+ const o = zone.offsetAt(localTs);
141
+ let utc = localTs - o * 6e4;
142
+ const o2 = zone.offsetAt(utc);
143
+ if (o === o2) return utc;
144
+ utc -= (o2 - o) * 6e4;
145
+ const o3 = zone.offsetAt(utc);
146
+ if (o2 === o3) return utc;
147
+ return localTs - Math.min(o2, o3) * 6e4;
148
+ }
149
+ function startOfDayInstant(day, zone) {
150
+ return toInstant(day, 0, zone);
151
+ }
152
+ function dayLengthMinutes(day, zone) {
153
+ return Math.round((startOfDayInstant(day + 1, zone) - startOfDayInstant(day, zone)) / 6e4);
154
+ }
155
+ var FLOATING_RE = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3})\d*)?)?$/;
156
+ var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
157
+ function parseDateInput(value, zone) {
158
+ if (value instanceof Date) {
159
+ const t2 = value.getTime();
160
+ return Number.isNaN(t2) ? null : { kind: "instant", ms: t2 };
161
+ }
162
+ if (typeof value === "number") return Number.isFinite(value) ? { kind: "instant", ms: value } : null;
163
+ const s = value.trim();
164
+ let m = DATE_ONLY_RE.exec(s);
165
+ if (m) return { kind: "day", day: dayFromYMD(Number(m[1]), Number(m[2]), Number(m[3])) };
166
+ m = FLOATING_RE.exec(s);
167
+ if (m) {
168
+ const day = dayFromYMD(Number(m[1]), Number(m[2]), Number(m[3]));
169
+ const minute = Number(m[4]) * 60 + Number(m[5]);
170
+ const extraMs = Number(m[6] ?? 0) * 1e3 + Number((m[7] ?? "0").padEnd(3, "0"));
171
+ return { kind: "instant", ms: toInstant(day, minute, zone) + extraMs };
172
+ }
173
+ const t = Date.parse(s);
174
+ return Number.isNaN(t) ? null : { kind: "instant", ms: t };
175
+ }
176
+ function formatFloating(ms, zone) {
177
+ const { day, minute } = wallTime(ms, zone);
178
+ const d = new Date(day * DAY_MS);
179
+ const pad = (n) => String(n).padStart(2, "0");
180
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}T${pad(Math.floor(minute / 60))}:${pad(minute % 60)}`;
181
+ }
182
+
183
+ // src/core/store.ts
184
+ var DEFAULTS = { defaultTimedMinutes: 60, defaultAllDayDays: 1 };
185
+ function signature(item) {
186
+ if (item.rev !== void 0) return `${item.id}#${String(item.rev)}`;
187
+ try {
188
+ return JSON.stringify(item, (_k, v) => v instanceof Date ? v.getTime() : v);
189
+ } catch {
190
+ return `${item.id}#${Math.random()}`;
191
+ }
192
+ }
193
+ var ItemStore = class _ItemStore {
194
+ zone;
195
+ opts;
196
+ items = /* @__PURE__ */ new Map();
197
+ timedByDay = /* @__PURE__ */ new Map();
198
+ spanning = /* @__PURE__ */ new Set();
199
+ pending = _ItemStore.emptyChange();
200
+ /** Bumped on every change; cheap staleness check for caches. */
201
+ version = 0;
202
+ constructor(zone, opts = {}) {
203
+ this.zone = zone;
204
+ this.opts = { ...DEFAULTS, ...opts };
205
+ }
206
+ static emptyChange() {
207
+ return { added: [], updated: [], removed: [], days: /* @__PURE__ */ new Set(), spans: false };
208
+ }
209
+ get size() {
210
+ return this.items.size;
211
+ }
212
+ get timeZone() {
213
+ return this.zone;
214
+ }
215
+ /** Re-resolves every item; used when the zone or defaults change. */
216
+ reconfigure(zone, opts = {}) {
217
+ this.zone = zone;
218
+ this.opts = { ...this.opts, ...opts };
219
+ const all = [...this.items.values()].map((n) => n.item);
220
+ this.items.clear();
221
+ this.timedByDay.clear();
222
+ this.spanning.clear();
223
+ for (const it of all) this.insert(it, "added");
224
+ this.pending.spans = true;
225
+ }
226
+ normalize(item) {
227
+ const zone = this.zone;
228
+ const s = parseDateInput(item.start, zone);
229
+ if (!s) return null;
230
+ const e = item.end != null && item.end !== "" ? parseDateInput(item.end, zone) : null;
231
+ const allDay = item.allDay ?? s.kind === "day";
232
+ const sig = signature(item);
233
+ if (allDay) {
234
+ const startDay2 = s.kind === "day" ? s.day : dayOf(s.ms, zone);
235
+ let endDay2 = startDay2 + this.opts.defaultAllDayDays;
236
+ if (e) {
237
+ const ed = e.kind === "day" ? e.day : dayOf(e.ms - 1, zone) + 1;
238
+ if (ed > startDay2) endDay2 = ed;
239
+ }
240
+ return {
241
+ id: item.id,
242
+ item,
243
+ allDay: true,
244
+ startDay: startDay2,
245
+ endDay: endDay2,
246
+ startMs: startOfDayInstant(startDay2, zone),
247
+ endMs: startOfDayInstant(endDay2, zone),
248
+ sig
249
+ };
250
+ }
251
+ const startMs = s.kind === "instant" ? s.ms : startOfDayInstant(s.day, zone);
252
+ let endMs = startMs + this.opts.defaultTimedMinutes * 6e4;
253
+ if (e) {
254
+ const em = e.kind === "instant" ? e.ms : startOfDayInstant(e.day, zone);
255
+ if (em > startMs) endMs = em;
256
+ }
257
+ const startDay = dayOf(startMs, zone);
258
+ const endDay = dayOf(endMs - 1, zone) + 1;
259
+ return { id: item.id, item, allDay: false, startMs, endMs, startDay, endDay, sig };
260
+ }
261
+ insert(item, kind) {
262
+ const n = this.normalize(item);
263
+ if (!n) return false;
264
+ this.items.set(n.id, n);
265
+ this.index(n);
266
+ this.pending[kind].push(n.id);
267
+ return true;
268
+ }
269
+ index(n) {
270
+ if (n.item.display === "none") return;
271
+ if (n.allDay) {
272
+ this.spanning.add(n.id);
273
+ this.pending.spans = true;
274
+ return;
275
+ }
276
+ if (n.endDay - n.startDay > 1) {
277
+ this.spanning.add(n.id);
278
+ this.pending.spans = true;
279
+ }
280
+ for (let d = n.startDay; d < n.endDay; d++) {
281
+ let set = this.timedByDay.get(d);
282
+ if (!set) this.timedByDay.set(d, set = /* @__PURE__ */ new Set());
283
+ set.add(n.id);
284
+ this.pending.days.add(d);
285
+ }
286
+ this.pending.spans = true;
287
+ }
288
+ unindex(n) {
289
+ if (this.spanning.delete(n.id)) this.pending.spans = true;
290
+ if (!n.allDay) {
291
+ for (let d = n.startDay; d < n.endDay; d++) {
292
+ this.timedByDay.get(d)?.delete(n.id);
293
+ this.pending.days.add(d);
294
+ }
295
+ this.pending.spans = true;
296
+ }
297
+ }
298
+ /** Replaces the whole item set, touching only items whose signature changed. */
299
+ setAll(items) {
300
+ const seen = /* @__PURE__ */ new Set();
301
+ for (const item of items) {
302
+ if (seen.has(item.id)) continue;
303
+ seen.add(item.id);
304
+ this.upsertOne(item);
305
+ }
306
+ for (const [id, n] of this.items) {
307
+ if (!seen.has(id)) {
308
+ this.unindex(n);
309
+ this.items.delete(id);
310
+ this.pending.removed.push(id);
311
+ }
312
+ }
313
+ return this.flush();
314
+ }
315
+ /** Upserts items without removing others. */
316
+ upsert(items) {
317
+ for (const item of items) this.upsertOne(item);
318
+ return this.flush();
319
+ }
320
+ remove(ids) {
321
+ for (const id of ids) {
322
+ const n = this.items.get(id);
323
+ if (!n) continue;
324
+ this.unindex(n);
325
+ this.items.delete(id);
326
+ this.pending.removed.push(id);
327
+ }
328
+ return this.flush();
329
+ }
330
+ upsertOne(item) {
331
+ const prev = this.items.get(item.id);
332
+ if (prev) {
333
+ if (prev.sig === signature(item)) {
334
+ if (prev.item !== item) this.items.set(item.id, { ...prev, item });
335
+ return;
336
+ }
337
+ this.unindex(prev);
338
+ this.items.delete(item.id);
339
+ if (!this.insert(item, "updated")) this.pending.removed.push(item.id);
340
+ return;
341
+ }
342
+ this.insert(item, "added");
343
+ }
344
+ flush() {
345
+ const out = this.pending;
346
+ this.pending = _ItemStore.emptyChange();
347
+ if (out.added.length || out.updated.length || out.removed.length || out.spans || out.days.size) this.version++;
348
+ return out;
349
+ }
350
+ get(id) {
351
+ return this.items.get(id);
352
+ }
353
+ all() {
354
+ return this.items.values();
355
+ }
356
+ /** Timed items touching `day`. */
357
+ timedOn(day) {
358
+ const set = this.timedByDay.get(day);
359
+ if (!set) return [];
360
+ const out = [];
361
+ for (const id of set) {
362
+ const n = this.items.get(id);
363
+ if (n) out.push(n);
364
+ }
365
+ return out;
366
+ }
367
+ /** All-day items (and, if asked, multi-day timed items) overlapping [startDay, endDay). */
368
+ spanningIn(startDay, endDay, includeTimedMultiDay) {
369
+ const out = [];
370
+ for (const id of this.spanning) {
371
+ const n = this.items.get(id);
372
+ if (!n || n.startDay >= endDay || n.endDay <= startDay) continue;
373
+ if (!n.allDay && !includeTimedMultiDay) continue;
374
+ out.push(n);
375
+ }
376
+ return out;
377
+ }
378
+ /** Every visible item overlapping [startDay, endDay). */
379
+ inRange(startDay, endDay) {
380
+ const seen = /* @__PURE__ */ new Set();
381
+ const out = [];
382
+ for (const n of this.spanningIn(startDay, endDay, true)) {
383
+ seen.add(n.id);
384
+ out.push(n);
385
+ }
386
+ for (let d = startDay; d < endDay; d++) {
387
+ for (const n of this.timedOn(d)) {
388
+ if (seen.has(n.id)) continue;
389
+ seen.add(n.id);
390
+ out.push(n);
391
+ }
392
+ }
393
+ return out;
394
+ }
395
+ };
396
+ function daysOfRange(startMs, endMs, zone) {
397
+ return [dayOf(startMs, zone), dayOf(Math.max(startMs, endMs - 1), zone) + 1];
398
+ }
399
+
400
+ // src/core/layout.ts
401
+ function segmentOn(n, day, zone) {
402
+ return segmentInWindow(n, day, 0, MINUTES_PER_DAY, zone);
403
+ }
404
+ function segmentInWindow(n, day, minMinute, maxMinute, zone) {
405
+ const winStart = toInstant(day, minMinute, zone);
406
+ const winEnd = toInstant(day, maxMinute, zone);
407
+ const s = Math.max(n.startMs, winStart);
408
+ const e = Math.min(n.endMs, winEnd);
409
+ const zeroLength = n.startMs === n.endMs && n.startMs >= winStart && n.startMs < winEnd;
410
+ if (e <= s && !zeroLength) return null;
411
+ const rel = (ms) => {
412
+ const w = wallTime(ms, zone);
413
+ return (w.day - day) * MINUTES_PER_DAY + w.minute;
414
+ };
415
+ const startMin = s === winStart ? minMinute : rel(s);
416
+ let endMin = e === winEnd ? maxMinute : rel(e);
417
+ if (endMin < startMin) endMin = Math.min(maxMinute, startMin + Math.round((e - s) / 6e4));
418
+ return {
419
+ item: n,
420
+ day,
421
+ startMin,
422
+ endMin,
423
+ isStart: n.startMs >= winStart,
424
+ isEnd: n.endMs <= winEnd
425
+ };
426
+ }
427
+ var defaultOrder = (a, b) => a.startMs - b.startMs || b.endMs - b.startMs - (a.endMs - a.startMs) || Number(b.allDay) - Number(a.allDay) || a.item.title.localeCompare(b.item.title);
428
+ function packTimed(segs, minMinutes, order = defaultOrder) {
429
+ const sorted = segs.map((s) => ({ ...s, end: Math.max(s.endMin, s.startMin + minMinutes) })).sort((a, b) => a.startMin - b.startMin || b.end - a.end || order(a.item, b.item));
430
+ const out = [];
431
+ let cluster = [];
432
+ let colEnds = [];
433
+ let clusterEnd = -Infinity;
434
+ let clusterIndex = 0;
435
+ const flush = () => {
436
+ if (cluster.length) clusterIndex++;
437
+ const cols = colEnds.length;
438
+ for (const p of cluster) {
439
+ p.cols = cols;
440
+ let span = 1;
441
+ while (p.col + span < cols && !cluster.some((o) => o.col === p.col + span && o.startMin < p.end && p.startMin < o.end)) span++;
442
+ p.span = span;
443
+ }
444
+ cluster = [];
445
+ colEnds = [];
446
+ };
447
+ for (const s of sorted) {
448
+ if (s.startMin >= clusterEnd) {
449
+ flush();
450
+ clusterEnd = -Infinity;
451
+ }
452
+ let col = colEnds.findIndex((end) => end <= s.startMin);
453
+ if (col < 0) {
454
+ col = colEnds.length;
455
+ colEnds.push(0);
456
+ }
457
+ colEnds[col] = s.end;
458
+ const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex };
459
+ cluster.push(p);
460
+ out.push(p);
461
+ clusterEnd = Math.max(clusterEnd, s.end);
462
+ }
463
+ flush();
464
+ for (const p of out) delete p.end;
465
+ return out;
466
+ }
467
+ function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTES_PER_DAY]) {
468
+ const plan = [];
469
+ const actual = [];
470
+ const background = [];
471
+ for (const n of items) {
472
+ const seg = segmentInWindow(n, day, window[0], window[1], zone);
473
+ if (!seg) continue;
474
+ if (n.item.display === "background") background.push(seg);
475
+ else if (n.item.lane === "actual") actual.push(seg);
476
+ else plan.push(seg);
477
+ }
478
+ return { day, plan: packTimed(plan, minMinutes, order), actual: packTimed(actual, minMinutes, order), background };
479
+ }
480
+ function layoutRow(items, days, order = defaultOrder) {
481
+ const first = days[0];
482
+ const last = days[days.length - 1];
483
+ const countByCol = new Array(days.length).fill(0);
484
+ if (first === void 0 || last === void 0) return { placements: [], countByCol, levels: 0 };
485
+ const colOf = (d) => {
486
+ let lo = 0;
487
+ let hi = days.length;
488
+ while (lo < hi) {
489
+ const mid = lo + hi >> 1;
490
+ if (days[mid] < d) lo = mid + 1;
491
+ else hi = mid;
492
+ }
493
+ return lo;
494
+ };
495
+ const sorted = items.filter((n) => n.startDay <= last && n.endDay > first).sort((a, b) => {
496
+ const sa = Math.max(a.startDay, first);
497
+ const sb = Math.max(b.startDay, first);
498
+ return sa - sb || b.endDay - b.startDay - (a.endDay - a.startDay) || order(a, b);
499
+ });
500
+ const levelEnds = [];
501
+ const placements = [];
502
+ for (const n of sorted) {
503
+ const startCol = colOf(n.startDay);
504
+ const endCol = colOf(n.endDay);
505
+ if (endCol <= startCol) continue;
506
+ let level = 0;
507
+ for (; ; level++) {
508
+ const occ = levelEnds[level] ??= [];
509
+ let free = true;
510
+ for (let c = startCol; c < endCol; c++) if (occ[c]) free = false;
511
+ if (free) {
512
+ for (let c = startCol; c < endCol; c++) occ[c] = 1;
513
+ break;
514
+ }
515
+ }
516
+ for (let c = startCol; c < endCol; c++) countByCol[c]++;
517
+ placements.push({
518
+ item: n,
519
+ startCol,
520
+ endCol,
521
+ level,
522
+ isStart: n.startDay >= days[startCol],
523
+ isEnd: n.endDay <= days[endCol - 1] + 1
524
+ });
525
+ }
526
+ return { placements, countByCol, levels: levelEnds.length };
527
+ }
528
+ function layoutLane(items, windowStart, windowEnd) {
529
+ const sorted = items.filter((n) => n.endMs > windowStart && n.startMs < windowEnd).sort((a, b) => a.startMs - b.startMs || b.endMs - a.endMs);
530
+ const levelEnds = [];
531
+ const bars = [];
532
+ for (const n of sorted) {
533
+ let level = levelEnds.findIndex((end) => end <= n.startMs);
534
+ if (level < 0) {
535
+ level = levelEnds.length;
536
+ levelEnds.push(0);
537
+ }
538
+ levelEnds[level] = Math.max(n.endMs, n.startMs + 1);
539
+ bars.push({
540
+ item: n,
541
+ startMs: Math.max(n.startMs, windowStart),
542
+ endMs: Math.min(n.endMs, windowEnd),
543
+ clippedStart: n.startMs < windowStart,
544
+ clippedEnd: n.endMs > windowEnd,
545
+ level
546
+ });
547
+ }
548
+ return { bars, levels: levelEnds.length };
549
+ }
550
+
551
+ // src/core/order.ts
552
+ var BUILTIN = {
553
+ start: (n) => n.startMs,
554
+ end: (n) => n.endMs,
555
+ duration: (n) => n.endMs - n.startMs,
556
+ allDay: (n) => n.allDay ? 0 : 1,
557
+ title: (n) => n.item.title,
558
+ id: (n) => n.id
559
+ };
560
+ function compare(a, b) {
561
+ if (a === b) return 0;
562
+ if (a == null) return 1;
563
+ if (b == null) return -1;
564
+ if (typeof a === "number" && typeof b === "number") return a - b;
565
+ const str = (v) => typeof v === "string" ? v : JSON.stringify(v) ?? "";
566
+ return str(a).localeCompare(str(b));
567
+ }
568
+ function parseOrder(spec) {
569
+ if (typeof spec === "function") return spec;
570
+ const parts = (spec ?? "start,-duration,allDay,title").split(",").map((p) => p.trim()).filter(Boolean).map((p) => {
571
+ const desc = p.startsWith("-");
572
+ const name = desc ? p.slice(1) : p;
573
+ const field = BUILTIN[name] ?? ((n) => {
574
+ const own = n.item[name];
575
+ if (own !== void 0) return own;
576
+ const data = n.item.data;
577
+ return data && typeof data === "object" ? data[name] : void 0;
578
+ });
579
+ return { field, sign: desc ? -1 : 1 };
580
+ });
581
+ return (a, b) => {
582
+ for (const { field, sign } of parts) {
583
+ const c = compare(field(a), field(b));
584
+ if (c) return c * sign;
585
+ }
586
+ return 0;
587
+ };
588
+ }
589
+
590
+ // src/core/plan.ts
591
+ function plannedMinutes(store, days) {
592
+ const zone = store.timeZone;
593
+ const out = /* @__PURE__ */ new Map();
594
+ for (const day of days) {
595
+ const start = toInstant(day, 0, zone);
596
+ const end = toInstant(day + 1, 0, zone);
597
+ let total = 0;
598
+ for (const n of store.timedOn(day)) {
599
+ if (n.item.display === "background" || n.item.lane === "actual") continue;
600
+ total += Math.max(0, Math.min(n.endMs, end) - Math.max(n.startMs, start)) / 6e4;
601
+ }
602
+ out.set(day, total);
603
+ }
604
+ return out;
605
+ }
606
+ function itemDependencies(item) {
607
+ return (item.dependsOn ?? []).map((d) => typeof d === "string" ? { id: d, type: "FS" } : { id: d.id, type: d.type ?? "FS" });
608
+ }
609
+ function dependencyType(fromEdge, toEdge) {
610
+ return `${fromEdge === "start" ? "S" : "F"}${toEdge === "start" ? "S" : "F"}`;
611
+ }
612
+ function dependencySatisfied(type, pred, succ) {
613
+ const from = type[0] === "S" ? pred.startMs : pred.endMs;
614
+ const to = type[1] === "S" ? succ.startMs : succ.endMs;
615
+ return from <= to;
616
+ }
617
+ function dependencyConflicts(store) {
618
+ const out = /* @__PURE__ */ new Map();
619
+ for (const n of store.all()) {
620
+ if (!n.item.dependsOn?.length) continue;
621
+ for (const dep of itemDependencies(n.item)) {
622
+ const d = store.get(dep.id);
623
+ if (!d || dependencySatisfied(dep.type, d, n)) continue;
624
+ const list = out.get(n.id) ?? [];
625
+ list.push(dep.id);
626
+ out.set(n.id, list);
627
+ }
628
+ }
629
+ return out;
630
+ }
631
+ function findFreeSlot(store, q) {
632
+ const zone = store.timeZone;
633
+ const snap = (q.snapMinutes ?? 15) * 6e4;
634
+ const need = q.durationMinutes * 6e4;
635
+ for (let day = q.fromDay; day < q.toDay; day++) {
636
+ if (q.skipDay?.(day)) continue;
637
+ const winStart = toInstant(day, q.window[0], zone);
638
+ const winEnd = toInstant(day, q.window[1], zone);
639
+ const busy = store.timedOn(day).filter((n) => n.item.display !== "background" && n.item.lane !== "actual" && !(q.ignore?.(n) ?? false)).map((n) => [n.startMs, n.endMs]).sort((a, b) => a[0] - b[0]);
640
+ let cursor = Math.max(winStart, q.notBefore ?? -Infinity);
641
+ cursor = Math.ceil(cursor / snap) * snap;
642
+ for (const [s, e] of busy) {
643
+ if (e <= cursor) continue;
644
+ if (s - cursor >= need && cursor + need <= winEnd) return { startMs: cursor, endMs: cursor + need };
645
+ cursor = Math.max(cursor, Math.ceil(e / snap) * snap);
646
+ if (cursor >= winEnd) break;
647
+ }
648
+ if (cursor + need <= winEnd) return { startMs: cursor, endMs: cursor + need };
649
+ }
650
+ return null;
651
+ }
652
+
653
+ export {
654
+ DAY_MS,
655
+ MINUTES_PER_DAY,
656
+ dayFromYMD,
657
+ ymdOfDay,
658
+ weekdayOf,
659
+ startOfWeek,
660
+ startOfMonth,
661
+ daysInMonth,
662
+ addMonths,
663
+ startOfYear,
664
+ formatDay,
665
+ parseDay,
666
+ isoWeekNumber,
667
+ parseDurationMinutes,
668
+ formatMinutes,
669
+ getTimeZone,
670
+ isValidTimeZone,
671
+ wallTime,
672
+ dayOf,
673
+ toInstant,
674
+ startOfDayInstant,
675
+ dayLengthMinutes,
676
+ parseDateInput,
677
+ formatFloating,
678
+ ItemStore,
679
+ daysOfRange,
680
+ segmentOn,
681
+ segmentInWindow,
682
+ defaultOrder,
683
+ packTimed,
684
+ layoutTimedDay,
685
+ layoutRow,
686
+ layoutLane,
687
+ parseOrder,
688
+ plannedMinutes,
689
+ itemDependencies,
690
+ dependencyType,
691
+ dependencySatisfied,
692
+ dependencyConflicts,
693
+ findFreeSlot
694
+ };
File without changes