@tasknotes/calendar 0.1.6 → 0.1.8
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/CHANGELOG.md +30 -0
- package/README.md +37 -0
- package/dist/calendar.css +387 -37
- package/dist/cjs/index.cjs +464 -83
- package/dist/cjs/react.cjs +464 -83
- package/dist/esm/{chunk-URE7QLU3.js → chunk-EXKFHPW2.js} +464 -83
- package/dist/esm/index.js +1 -1
- package/dist/esm/react.js +1 -1
- package/dist/types/core/types.d.cts +1 -0
- package/dist/types/core/types.d.ts +1 -0
- package/dist/types/dom/calendar.d.cts +15 -2
- package/dist/types/dom/calendar.d.ts +15 -2
- package/dist/types/dom/context.d.cts +1 -1
- package/dist/types/dom/context.d.ts +1 -1
- package/dist/types/dom/options.d.cts +5 -2
- package/dist/types/dom/options.d.ts +5 -2
- package/dist/types/dom/views/daygrid.d.cts +1 -1
- package/dist/types/dom/views/daygrid.d.ts +1 -1
- package/dist/types/dom/views/timegrid.d.cts +4 -0
- package/dist/types/dom/views/timegrid.d.ts +4 -0
- package/dist/types/dom/views/year.d.cts +1 -0
- package/dist/types/dom/views/year.d.ts +1 -0
- package/package.json +1 -1
|
@@ -152,6 +152,7 @@ var DEFAULT_TEXT = {
|
|
|
152
152
|
allDay: "All day",
|
|
153
153
|
noItems: "Nothing scheduled",
|
|
154
154
|
more: (n) => `+${n} more`,
|
|
155
|
+
appointments: (n) => `${n} appointments`,
|
|
155
156
|
itemCount: (n) => n === 1 ? "1 item" : `${n} items`,
|
|
156
157
|
weekNumber: (n) => `W${n}`,
|
|
157
158
|
views: { week: "Week", day: "Day", days: "Days", month: "Month", list: "List", year: "Year", timeline: "Timeline" },
|
|
@@ -221,6 +222,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
221
222
|
"dateStrip",
|
|
222
223
|
"listLeadTitle",
|
|
223
224
|
"listAddText",
|
|
225
|
+
"listEmptyContent",
|
|
224
226
|
"monthDisplay",
|
|
225
227
|
"timeZone",
|
|
226
228
|
"locale",
|
|
@@ -357,8 +359,9 @@ var DayGridView = class {
|
|
|
357
359
|
constructor(ctx) {
|
|
358
360
|
this.ctx = ctx;
|
|
359
361
|
const { doc, opts, days } = ctx;
|
|
360
|
-
this.dotsMode = opts.monthDisplay === "dots";
|
|
362
|
+
this.dotsMode = opts.monthDisplay === "dots" || opts.monthDisplay === "chips";
|
|
361
363
|
this.el = h(doc, "div", this.dotsMode ? "tnc-dg tnc-dg--dots" : "tnc-dg");
|
|
364
|
+
this.el.classList.toggle("tnc-dg--chips", opts.monthDisplay === "chips");
|
|
362
365
|
this.scroller = h(doc, "div", "tnc-dg-scroll");
|
|
363
366
|
this.grid = h(doc, "div", "tnc-dg-grid", { role: "grid", "aria-label": ctx.view.title });
|
|
364
367
|
const perWeek = this.daysPerWeek();
|
|
@@ -388,14 +391,14 @@ var DayGridView = class {
|
|
|
388
391
|
const cell = h(doc, "div", cls.join(" "), { role: "gridcell", "aria-label": ctx.fmt.longDate(d), "data-date": arg.dateStr });
|
|
389
392
|
if (this.dotsMode) cell.tabIndex = 0;
|
|
390
393
|
const num = ctx.fmt.dayNumber(d);
|
|
391
|
-
const label = ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num}` : num;
|
|
394
|
+
const label = !this.dotsMode && ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num}` : num;
|
|
392
395
|
cell.append(
|
|
393
396
|
h(
|
|
394
397
|
doc,
|
|
395
398
|
"div",
|
|
396
399
|
"tnc-dg-top",
|
|
397
400
|
null,
|
|
398
|
-
opts.navLinks ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
401
|
+
opts.navLinks && !this.dotsMode ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
399
402
|
)
|
|
400
403
|
);
|
|
401
404
|
dates.append(cell);
|
|
@@ -409,20 +412,20 @@ var DayGridView = class {
|
|
|
409
412
|
layer.append(content);
|
|
410
413
|
row.append(dates, layer);
|
|
411
414
|
this.grid.append(row);
|
|
412
|
-
const dots =
|
|
415
|
+
const dots = cells.map((cell) => {
|
|
413
416
|
const box = h(doc, "div", "tnc-dg-dots", { "aria-hidden": "true" });
|
|
414
417
|
if (opts.dayMaxEvents === false) box.classList.add("is-unlimited");
|
|
415
418
|
const count = h(doc, "span", "tnc-dg-dots-more");
|
|
416
419
|
cell.append(box);
|
|
417
420
|
return { box, pool: new ItemPool(ctx, box), count };
|
|
418
|
-
})
|
|
421
|
+
});
|
|
419
422
|
this.weeks.push({ days: wdays, row, cells, layer, sel, more, pool: new ItemPool(ctx, content), dots });
|
|
420
423
|
}
|
|
421
424
|
this.scroller.append(this.grid);
|
|
422
425
|
this.el.append(this.scroller);
|
|
423
426
|
this.el.classList.toggle("tnc-dg--auto", opts.height === "auto");
|
|
424
427
|
const RO = ctx.win.ResizeObserver;
|
|
425
|
-
if (RO
|
|
428
|
+
if (RO) {
|
|
426
429
|
this.resizeObs = new RO(() => {
|
|
427
430
|
if (this.sizeKey() !== this.lastHeights) this.render(null);
|
|
428
431
|
});
|
|
@@ -453,14 +456,35 @@ var DayGridView = class {
|
|
|
453
456
|
}
|
|
454
457
|
render(_change) {
|
|
455
458
|
const { ctx } = this;
|
|
459
|
+
this.dotsMode = ctx.opts.monthDisplay !== "full" || this.grid.clientWidth / this.daysPerWeek() < 72;
|
|
460
|
+
this.el.classList.toggle("tnc-dg--dots", this.dotsMode);
|
|
461
|
+
this.el.classList.toggle("tnc-dg--compact", this.dotsMode && ctx.opts.monthDisplay === "full");
|
|
462
|
+
this.el.classList.toggle("tnc-dg--chips", ctx.opts.monthDisplay === "chips" && this.weeks[0].dots[0].box.clientHeight >= 44);
|
|
456
463
|
const cs = ctx.win.getComputedStyle(this.el);
|
|
457
464
|
this.barPx = px(cs, "--tnc-bar-h", 20);
|
|
458
465
|
this.topPx = this.weeks[0]?.cells[0]?.querySelector(".tnc-dg-top")?.offsetHeight || px(cs, "--tnc-dg-top-h", 24);
|
|
459
466
|
this.morePx = this.measureMore(cs);
|
|
460
467
|
this.lastHeights = this.sizeKey();
|
|
461
468
|
for (const w of this.weeks) {
|
|
462
|
-
|
|
463
|
-
|
|
469
|
+
for (const cell of w.cells) {
|
|
470
|
+
if (this.dotsMode) cell.tabIndex = 0;
|
|
471
|
+
else cell.removeAttribute("tabindex");
|
|
472
|
+
if (this.dotsMode && ctx.opts.monthDisplay === "full") cell.setAttribute("aria-haspopup", "dialog");
|
|
473
|
+
else cell.removeAttribute("aria-haspopup");
|
|
474
|
+
}
|
|
475
|
+
for (const cell of w.dots) cell.box.hidden = !this.dotsMode;
|
|
476
|
+
if (this.dotsMode) {
|
|
477
|
+
w.pool.begin();
|
|
478
|
+
w.pool.end();
|
|
479
|
+
w.more.replaceChildren();
|
|
480
|
+
this.renderDots(w);
|
|
481
|
+
} else {
|
|
482
|
+
for (const cell of w.dots) {
|
|
483
|
+
cell.pool.begin();
|
|
484
|
+
cell.pool.end();
|
|
485
|
+
}
|
|
486
|
+
this.renderWeek(w);
|
|
487
|
+
}
|
|
464
488
|
}
|
|
465
489
|
}
|
|
466
490
|
/** Height of a "+N more" link as the host styles it (`--tnc-more-h`, or a taller min-height). */
|
|
@@ -546,19 +570,28 @@ var DayGridView = class {
|
|
|
546
570
|
});
|
|
547
571
|
}
|
|
548
572
|
w.pool.end();
|
|
549
|
-
w.more.
|
|
573
|
+
const existing = new Map(Array.from(w.more.children, (element) => [element.getAttribute("data-day"), element]));
|
|
550
574
|
hidden.forEach((items, col) => {
|
|
551
575
|
if (!items.length) return;
|
|
552
576
|
const day = w.days[col];
|
|
553
|
-
const
|
|
577
|
+
const key = String(day);
|
|
578
|
+
let link = existing.get(key);
|
|
579
|
+
if (!link) {
|
|
580
|
+
link = h(ctx.doc, "button", "tnc-more", { type: "button", "aria-haspopup": "dialog", "data-day": key });
|
|
581
|
+
const anchor = link;
|
|
582
|
+
link.addEventListener("click", (ev) => ctx.openMore(day, anchor, ev));
|
|
583
|
+
w.more.append(link);
|
|
584
|
+
}
|
|
585
|
+
existing.delete(key);
|
|
586
|
+
link.setAttribute("aria-label", ctx.text.more(items.length));
|
|
587
|
+
link.textContent = w.row.clientWidth / n < 72 ? `+${new Intl.NumberFormat(ctx.opts.locale).format(items.length)}` : ctx.text.more(items.length);
|
|
554
588
|
setStyle(link, {
|
|
555
589
|
left: `calc(${pct(col / n)} + 2px)`,
|
|
556
590
|
width: `calc(${pct(1 / n)} - 4px)`,
|
|
557
591
|
top: `calc(var(--tnc-dg-top-h) + ${visible(col)} * (var(--tnc-bar-h) + ${BAR_GAP}px))`
|
|
558
592
|
});
|
|
559
|
-
link.addEventListener("click", (ev) => ctx.openMore(day, link, ev));
|
|
560
|
-
w.more.append(link);
|
|
561
593
|
});
|
|
594
|
+
for (const link of existing.values()) link.remove();
|
|
562
595
|
const step = this.barPx + BAR_GAP;
|
|
563
596
|
let need = maxLevel >= 0 ? (maxLevel + 1) * step - BAR_GAP : 0;
|
|
564
597
|
hidden.forEach((items, col) => {
|
|
@@ -581,10 +614,11 @@ var DayGridView = class {
|
|
|
581
614
|
const { dayMaxEvents } = ctx.opts;
|
|
582
615
|
const first = w.dots[0].box;
|
|
583
616
|
const cs = ctx.win.getComputedStyle(first);
|
|
584
|
-
const
|
|
617
|
+
const chips = this.el.classList.contains("tnc-dg--chips");
|
|
618
|
+
const size = chips ? px(cs, "--tnc-chip-h", 14) : px(cs, "--tnc-dot-size", 8);
|
|
585
619
|
const gap = px(cs, "--tnc-dot-gap", 3);
|
|
586
620
|
const step = size + gap;
|
|
587
|
-
const perRow = Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
621
|
+
const perRow = chips ? 1 : Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
588
622
|
const rows = Math.max(1, Math.floor((first.clientHeight + gap) / step));
|
|
589
623
|
const fits = dayMaxEvents === false ? Infinity : perRow * rows;
|
|
590
624
|
const cap = typeof dayMaxEvents === "number" && dayMaxEvents > 0 ? Math.min(dayMaxEvents, fits) : fits;
|
|
@@ -593,7 +627,7 @@ var DayGridView = class {
|
|
|
593
627
|
w.days.forEach((day, i) => {
|
|
594
628
|
const cell = w.dots[i];
|
|
595
629
|
const items = this.itemsOn(day);
|
|
596
|
-
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(
|
|
630
|
+
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(1, cap - (chips ? 1 : 2)) : Math.max(1, cap);
|
|
597
631
|
cell.pool.begin();
|
|
598
632
|
const els = items.slice(0, shown).map((n) => cell.pool.place(n.id, { n, day, isStart: n.startDay >= day, isEnd: n.endDay <= day + 1, variant: "indicator", mirror: false }));
|
|
599
633
|
cell.pool.end();
|
|
@@ -710,6 +744,7 @@ var ListView = class {
|
|
|
710
744
|
const leadItems = [];
|
|
711
745
|
const inLead = /* @__PURE__ */ new Set();
|
|
712
746
|
const keep = /* @__PURE__ */ new Set();
|
|
747
|
+
let itemCount = 0;
|
|
713
748
|
let prev = null;
|
|
714
749
|
for (const day of days) {
|
|
715
750
|
let items = this.itemsOn(day);
|
|
@@ -723,7 +758,7 @@ var ListView = class {
|
|
|
723
758
|
return false;
|
|
724
759
|
});
|
|
725
760
|
}
|
|
726
|
-
if (!items.length && !agenda) continue;
|
|
761
|
+
if (!items.length && !agenda && opts.listDayCount !== 1) continue;
|
|
727
762
|
keep.add(day);
|
|
728
763
|
let section = this.sections.get(day);
|
|
729
764
|
let pool = this.pools.get(day);
|
|
@@ -742,6 +777,7 @@ var ListView = class {
|
|
|
742
777
|
this.pools.set(day, pool);
|
|
743
778
|
}
|
|
744
779
|
section.classList.toggle("is-empty", !items.length);
|
|
780
|
+
itemCount += items.length;
|
|
745
781
|
this.fill(pool, items, () => day);
|
|
746
782
|
if (prev ? prev.nextSibling !== section : this.body.firstChild !== section) {
|
|
747
783
|
if (prev) prev.after(section);
|
|
@@ -757,11 +793,14 @@ var ListView = class {
|
|
|
757
793
|
this.sections.delete(day);
|
|
758
794
|
}
|
|
759
795
|
this.renderLead(leadItems);
|
|
760
|
-
let empty = this.body.querySelector("
|
|
761
|
-
if (!
|
|
796
|
+
let empty = this.body.querySelector(".tnc-list-empty");
|
|
797
|
+
if (!itemCount && !leadItems.length && (!agenda || opts.listEmptyContent)) {
|
|
762
798
|
if (!empty) {
|
|
763
799
|
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
764
|
-
|
|
800
|
+
const content = ctx.opts.listEmptyContent?.();
|
|
801
|
+
if (content) empty.replaceChildren(content);
|
|
802
|
+
if (this.sections.size === 1) this.sections.values().next().value.append(empty);
|
|
803
|
+
else this.body.prepend(empty);
|
|
765
804
|
}
|
|
766
805
|
} else empty?.remove();
|
|
767
806
|
this.ctx.strip?.draw();
|
|
@@ -819,7 +858,7 @@ var ListView = class {
|
|
|
819
858
|
const section = day === this.ctx.days[0] && this.lead ? this.lead.section : this.sections.get(day);
|
|
820
859
|
if (!section) return;
|
|
821
860
|
this.ctx.strip?.measure();
|
|
822
|
-
section.
|
|
861
|
+
this.scroller.scrollTop += section.getBoundingClientRect().top - this.scroller.getBoundingClientRect().top;
|
|
823
862
|
this.ctx.strip?.setFocus(day);
|
|
824
863
|
this.pinned = true;
|
|
825
864
|
}
|
|
@@ -908,8 +947,7 @@ var DateStrip = class {
|
|
|
908
947
|
head.append(this.title, this.todayBtn, prev, next, this.slot);
|
|
909
948
|
this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
|
|
910
949
|
this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
|
|
911
|
-
const handle = h(doc, "
|
|
912
|
-
handle.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
950
|
+
const handle = h(doc, "div", "tnc-strip-handle", { "aria-hidden": "true" });
|
|
913
951
|
this.el.append(head, this.dows, this.grid, handle);
|
|
914
952
|
this.grid.addEventListener("click", this.onClick);
|
|
915
953
|
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
@@ -1109,6 +1147,8 @@ var TimeGridView = class {
|
|
|
1109
1147
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1110
1148
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1111
1149
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1150
|
+
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1151
|
+
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
1112
1152
|
this.top = h(doc, "div", "tnc-tg-top");
|
|
1113
1153
|
this.headRow = h(doc, "div", "tnc-tg-head tnc-tg-cols", { role: "row" });
|
|
1114
1154
|
this.top.append(this.headRow);
|
|
@@ -1136,7 +1176,7 @@ var TimeGridView = class {
|
|
|
1136
1176
|
} else {
|
|
1137
1177
|
this.body = this.gutter = null;
|
|
1138
1178
|
}
|
|
1139
|
-
this.el.append(this.scroller);
|
|
1179
|
+
this.el.append(this.dayNavigation, this.scroller);
|
|
1140
1180
|
this.build();
|
|
1141
1181
|
if (showGrid && opts.height === "fill") {
|
|
1142
1182
|
const RO = ctx.win.ResizeObserver;
|
|
@@ -1149,6 +1189,7 @@ var TimeGridView = class {
|
|
|
1149
1189
|
ctx;
|
|
1150
1190
|
el;
|
|
1151
1191
|
scroller;
|
|
1192
|
+
dayNavigation;
|
|
1152
1193
|
top;
|
|
1153
1194
|
headRow;
|
|
1154
1195
|
allDayRow;
|
|
@@ -1204,6 +1245,9 @@ var TimeGridView = class {
|
|
|
1204
1245
|
head.append(cap);
|
|
1205
1246
|
}
|
|
1206
1247
|
this.headRow.append(head);
|
|
1248
|
+
const dayButton = h(doc, "button", "tnc-btn", { type: "button", "aria-label": ctx.fmt.longDate(day) }, `${ctx.fmt.weekdayShort(day)} ${ctx.fmt.dayNumber(day)}`);
|
|
1249
|
+
dayButton.addEventListener("click", () => this.revealDay(day));
|
|
1250
|
+
this.dayNavigation.append(dayButton);
|
|
1207
1251
|
opts.dayHeaderDidMount?.({ ...arg, el: head });
|
|
1208
1252
|
const col = h(doc, "div", ["tnc-col", ...ctx.dayClasses(day)].join(" "), { role: "cell", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr });
|
|
1209
1253
|
const bgLayer = h(doc, "div", "tnc-col-bg");
|
|
@@ -1220,6 +1264,7 @@ var TimeGridView = class {
|
|
|
1220
1264
|
this.body?.append(col);
|
|
1221
1265
|
return {
|
|
1222
1266
|
day,
|
|
1267
|
+
collisionColumns: 1,
|
|
1223
1268
|
head,
|
|
1224
1269
|
col,
|
|
1225
1270
|
plan: new ItemPool(ctx, planLayer),
|
|
@@ -1276,12 +1321,17 @@ var TimeGridView = class {
|
|
|
1276
1321
|
/* --------------------------------------------------------------- render */
|
|
1277
1322
|
render(change) {
|
|
1278
1323
|
this.renderAllDay();
|
|
1279
|
-
if (!this.body)
|
|
1324
|
+
if (!this.body) {
|
|
1325
|
+
this.updateDayNavigation();
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1280
1328
|
const dirty = change ? change.days : null;
|
|
1281
1329
|
for (const c of this.cols) {
|
|
1282
1330
|
if (dirty && !dirty.has(c.day) && !(this.ctx.slot.max > MINUTES_PER_DAY && dirty.has(c.day + 1))) continue;
|
|
1283
1331
|
this.renderColumn(c);
|
|
1284
1332
|
}
|
|
1333
|
+
this.el.style.setProperty("--tnc-collision-columns", String(Math.max(1, ...this.cols.map((column) => column.collisionColumns))));
|
|
1334
|
+
this.updateDayNavigation();
|
|
1285
1335
|
this.renderCapacity();
|
|
1286
1336
|
this.tick();
|
|
1287
1337
|
}
|
|
@@ -1399,6 +1449,11 @@ var TimeGridView = class {
|
|
|
1399
1449
|
}
|
|
1400
1450
|
const minMinutes = opts.eventMinHeight / this.ppm;
|
|
1401
1451
|
const layout = layoutTimedDay(items, c.day, zone, minMinutes, ctx.order, [slot.min, slot.max]);
|
|
1452
|
+
c.collisionColumns = Math.max(
|
|
1453
|
+
1,
|
|
1454
|
+
...layout.plan.map((placement) => opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols),
|
|
1455
|
+
...layout.actual.filter((placement) => !placement.split).map((placement) => placement.cols)
|
|
1456
|
+
);
|
|
1402
1457
|
const total = this.minutes;
|
|
1403
1458
|
const top = (m) => pct((m - slot.min) / total);
|
|
1404
1459
|
const height = (s) => pct((s.endMin - s.startMin) / total);
|
|
@@ -1410,14 +1465,22 @@ var TimeGridView = class {
|
|
|
1410
1465
|
c.bg.end();
|
|
1411
1466
|
const hasActual = layout.actual.length > 0;
|
|
1412
1467
|
c.col.classList.toggle("has-actual", hasActual);
|
|
1468
|
+
for (const link of c.more.children) link.setAttribute("data-stale", "true");
|
|
1413
1469
|
this.placeLane(c.plan, layout.plan, c, top, height, "plan");
|
|
1414
1470
|
this.placeLane(c.actual, layout.actual, c, top, height, "actual");
|
|
1471
|
+
for (const link of [...c.more.children]) if (link.hasAttribute("data-stale")) link.remove();
|
|
1415
1472
|
}
|
|
1416
1473
|
placeLane(pool, placements, c, top, height, lane) {
|
|
1417
1474
|
const { ctx } = this;
|
|
1418
1475
|
const { opts } = ctx;
|
|
1419
|
-
const
|
|
1420
|
-
const
|
|
1476
|
+
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
1477
|
+
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
1478
|
+
const collapse = placements.some((placement) => {
|
|
1479
|
+
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
1480
|
+
return columns > 1 && columns * readableWidth > laneWidth;
|
|
1481
|
+
});
|
|
1482
|
+
const withMore = lane === "plan" || collapse;
|
|
1483
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
1421
1484
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
1422
1485
|
pool.begin();
|
|
1423
1486
|
for (const p of placements) {
|
|
@@ -1443,21 +1506,46 @@ var TimeGridView = class {
|
|
|
1443
1506
|
top: top(p.startMin),
|
|
1444
1507
|
height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
1445
1508
|
...inLane(lane, p.split, left, width),
|
|
1509
|
+
"--tnc-event-readable-width": pct(span / cols / width),
|
|
1446
1510
|
"z-index": String(p.col + 1)
|
|
1447
1511
|
});
|
|
1448
1512
|
}
|
|
1449
1513
|
pool.end();
|
|
1450
1514
|
if (!withMore) return;
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1515
|
+
for (const [cluster, rec] of hiddenByCluster) {
|
|
1516
|
+
const key = `${lane}-${cluster}`;
|
|
1517
|
+
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
1518
|
+
if (!link) {
|
|
1519
|
+
link = h(ctx.doc, "button", "tnc-more tnc-more--stack", { type: "button", "aria-haspopup": "dialog", "data-cluster": key });
|
|
1520
|
+
const anchor = link;
|
|
1521
|
+
link.addEventListener("click", (ev) => ctx.openMore(c.day, anchor, ev, JSON.parse(anchor.dataset.itemIds ?? "[]")));
|
|
1522
|
+
c.more.append(link);
|
|
1523
|
+
}
|
|
1524
|
+
link.removeAttribute("data-stale");
|
|
1525
|
+
link.dataset.itemIds = JSON.stringify(placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.id));
|
|
1526
|
+
link.classList.toggle("tnc-more--collision", collapse);
|
|
1527
|
+
link.textContent = `+${rec.count}`;
|
|
1528
|
+
if (collapse) {
|
|
1529
|
+
const items = placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.item.title);
|
|
1530
|
+
const count = ctx.text.appointments(rec.count);
|
|
1531
|
+
const interval = `${ctx.fmt.time(rec.start)} \u2013 ${ctx.fmt.time(rec.end)}`;
|
|
1532
|
+
link.replaceChildren(
|
|
1533
|
+
h(ctx.doc, "span", "tnc-collision-count", null, String(rec.count)),
|
|
1534
|
+
h(ctx.doc, "span", "tnc-collision-range", null, interval),
|
|
1535
|
+
h(ctx.doc, "span", "tnc-collision-title", { "aria-hidden": "true" }, items.join(" \xB7 "))
|
|
1536
|
+
);
|
|
1537
|
+
link.setAttribute("aria-label", `${count}, ${interval}: ${items.join(", ")}`);
|
|
1538
|
+
link.title = link.getAttribute("aria-label");
|
|
1539
|
+
} else {
|
|
1540
|
+
link.removeAttribute("aria-label");
|
|
1541
|
+
link.removeAttribute("title");
|
|
1542
|
+
}
|
|
1454
1543
|
const cols = maxStack;
|
|
1455
1544
|
setStyle(link, {
|
|
1456
1545
|
top: top(rec.start),
|
|
1546
|
+
height: collapse ? pct((rec.end - rec.start) / this.minutes) : "",
|
|
1457
1547
|
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
1458
1548
|
});
|
|
1459
|
-
link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
|
|
1460
|
-
c.more.append(link);
|
|
1461
1549
|
}
|
|
1462
1550
|
}
|
|
1463
1551
|
meta(s, bg) {
|
|
@@ -1522,12 +1610,10 @@ var TimeGridView = class {
|
|
|
1522
1610
|
const r2 = cells[i2].getBoundingClientRect();
|
|
1523
1611
|
if (x >= r2.left && x < r2.right) return i2;
|
|
1524
1612
|
}
|
|
1525
|
-
const firstR = cells[0]?.getBoundingClientRect();
|
|
1526
|
-
const lastR = cells[cells.length - 1]?.getBoundingClientRect();
|
|
1527
|
-
if (firstR && x < firstR.left) return 0;
|
|
1528
|
-
if (lastR && x >= lastR.right) return cells.length - 1;
|
|
1529
1613
|
return -1;
|
|
1530
1614
|
};
|
|
1615
|
+
const bounds = this.scroller.getBoundingClientRect();
|
|
1616
|
+
if (x < bounds.left || x >= bounds.right || y < bounds.top || y >= bounds.bottom) return null;
|
|
1531
1617
|
if (this.allDayRow) {
|
|
1532
1618
|
const r2 = this.allDayRow.getBoundingClientRect();
|
|
1533
1619
|
if (y >= r2.top && y < r2.bottom || !this.body && y >= r2.top) {
|
|
@@ -1568,6 +1654,26 @@ var TimeGridView = class {
|
|
|
1568
1654
|
}
|
|
1569
1655
|
resize() {
|
|
1570
1656
|
this.fitRows();
|
|
1657
|
+
this.render(null);
|
|
1658
|
+
}
|
|
1659
|
+
revealDay(day) {
|
|
1660
|
+
const column = this.cols.find((candidate) => candidate.day === day);
|
|
1661
|
+
if (!column) return;
|
|
1662
|
+
const bounds = column.head.getBoundingClientRect();
|
|
1663
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
1664
|
+
this.scroller.scrollLeft += bounds.left - viewport.left - (viewport.width - bounds.width) / 2;
|
|
1665
|
+
this.updateDayNavigation();
|
|
1666
|
+
}
|
|
1667
|
+
updateDayNavigation() {
|
|
1668
|
+
this.dayNavigation.hidden = this.scroller.scrollWidth <= this.scroller.clientWidth + 1;
|
|
1669
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
1670
|
+
this.cols.forEach((column, index) => {
|
|
1671
|
+
const bounds = column.head.getBoundingClientRect();
|
|
1672
|
+
const visible = bounds.left >= viewport.left && bounds.right <= viewport.right;
|
|
1673
|
+
const button = this.dayNavigation.children[index];
|
|
1674
|
+
button?.classList.toggle("is-offscreen", !visible);
|
|
1675
|
+
button?.setAttribute("aria-pressed", String(visible));
|
|
1676
|
+
});
|
|
1571
1677
|
}
|
|
1572
1678
|
scrollToTime(minute) {
|
|
1573
1679
|
if (!this.body) return;
|
|
@@ -2230,6 +2336,14 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2230
2336
|
setScroll(s) {
|
|
2231
2337
|
this.scroller.scrollTop = s.top;
|
|
2232
2338
|
}
|
|
2339
|
+
revealDay(day) {
|
|
2340
|
+
const month = this.cells.find((cell) => cell.day === day)?.el.closest(".tnc-ym");
|
|
2341
|
+
if (!month) return;
|
|
2342
|
+
const bounds = month.getBoundingClientRect();
|
|
2343
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2344
|
+
if (bounds.top < viewport.top) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
2345
|
+
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.bottom - viewport.bottom;
|
|
2346
|
+
}
|
|
2233
2347
|
elementsFor(_id) {
|
|
2234
2348
|
return [];
|
|
2235
2349
|
}
|
|
@@ -2309,6 +2423,11 @@ var Calendar = class {
|
|
|
2309
2423
|
conflicts = /* @__PURE__ */ new Map();
|
|
2310
2424
|
slot = { min: 0, max: MINUTES_PER_DAY, duration: 30, snap: 30, scroll: 480 };
|
|
2311
2425
|
draggingId = null;
|
|
2426
|
+
listDragPreview = null;
|
|
2427
|
+
dragSources = [];
|
|
2428
|
+
dragScrollFrame = null;
|
|
2429
|
+
dragScrollOrigins = /* @__PURE__ */ new Map();
|
|
2430
|
+
itemWriteStates = /* @__PURE__ */ new Map();
|
|
2312
2431
|
dragRow = null;
|
|
2313
2432
|
strip = null;
|
|
2314
2433
|
stripSize = null;
|
|
@@ -2322,6 +2441,7 @@ var Calendar = class {
|
|
|
2322
2441
|
session = null;
|
|
2323
2442
|
selection = null;
|
|
2324
2443
|
suppressClick = false;
|
|
2444
|
+
cancelledPointerId = null;
|
|
2325
2445
|
selectedIds = /* @__PURE__ */ new Set();
|
|
2326
2446
|
popover = null;
|
|
2327
2447
|
timer = null;
|
|
@@ -2333,8 +2453,13 @@ var Calendar = class {
|
|
|
2333
2453
|
this.resizeObs?.disconnect();
|
|
2334
2454
|
const RO = this.win.ResizeObserver;
|
|
2335
2455
|
if (RO) {
|
|
2336
|
-
this.resizeObs = new RO(() =>
|
|
2456
|
+
this.resizeObs = new RO(() => {
|
|
2457
|
+
this.syncToolbarViewDisplay();
|
|
2458
|
+
this.current?.resize?.();
|
|
2459
|
+
this.positionMore();
|
|
2460
|
+
});
|
|
2337
2461
|
this.resizeObs.observe(this.root);
|
|
2462
|
+
this.resizeObs.observe(this.toolbarEl);
|
|
2338
2463
|
}
|
|
2339
2464
|
}
|
|
2340
2465
|
onAdoptCheck = () => {
|
|
@@ -2353,9 +2478,11 @@ var Calendar = class {
|
|
|
2353
2478
|
if (this.frame !== null) old.cancelAnimationFrame(this.frame);
|
|
2354
2479
|
this.timer = this.frame = null;
|
|
2355
2480
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
2481
|
+
this.listenMoreGeometry(false);
|
|
2356
2482
|
this.doc = doc;
|
|
2357
2483
|
this.win = doc.defaultView ?? old;
|
|
2358
2484
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
2485
|
+
this.listenMoreGeometry(true);
|
|
2359
2486
|
this.observeResize();
|
|
2360
2487
|
this.rebuild(false);
|
|
2361
2488
|
this.scheduleTick();
|
|
@@ -2459,7 +2586,12 @@ var Calendar = class {
|
|
|
2459
2586
|
gotoDate(date) {
|
|
2460
2587
|
const day = this.toDay(date);
|
|
2461
2588
|
if (day === null) return;
|
|
2589
|
+
const sameMonth = this.opts.view === "month" && startOfMonth(day) === startOfMonth(this.anchor);
|
|
2462
2590
|
this.anchor = day;
|
|
2591
|
+
if (sameMonth) {
|
|
2592
|
+
this.view = { ...this.view, anchor: day };
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2463
2595
|
this.rebuild(true);
|
|
2464
2596
|
}
|
|
2465
2597
|
prev() {
|
|
@@ -2473,6 +2605,7 @@ var Calendar = class {
|
|
|
2473
2605
|
goToday() {
|
|
2474
2606
|
this.anchor = this.todayDay();
|
|
2475
2607
|
this.rebuild(true);
|
|
2608
|
+
if (this.current instanceof TimeGridView) this.current.revealDay(this.anchor);
|
|
2476
2609
|
}
|
|
2477
2610
|
/** The anchor date, as local midnight of that civil day in the calendar zone. */
|
|
2478
2611
|
getDate() {
|
|
@@ -2491,6 +2624,7 @@ var Calendar = class {
|
|
|
2491
2624
|
/** Re-measures (e.g. after the host was shown or resized by a parent). */
|
|
2492
2625
|
updateSize() {
|
|
2493
2626
|
this.current?.resize?.();
|
|
2627
|
+
this.positionMore();
|
|
2494
2628
|
}
|
|
2495
2629
|
getScroll() {
|
|
2496
2630
|
return this.current?.getScroll() ?? { top: 0, left: 0 };
|
|
@@ -2522,7 +2656,10 @@ var Calendar = class {
|
|
|
2522
2656
|
}
|
|
2523
2657
|
const view = this.current;
|
|
2524
2658
|
if (view instanceof TimelineView) view.scrollToInstant(ms, opts.align ?? "center", opts.smooth ?? false);
|
|
2525
|
-
else if (view instanceof TimeGridView
|
|
2659
|
+
else if (view instanceof TimeGridView) {
|
|
2660
|
+
view.revealDay(day);
|
|
2661
|
+
if (p.kind !== "day") view.scrollToTime(wallTime(ms, this.zone).minute);
|
|
2662
|
+
}
|
|
2526
2663
|
}
|
|
2527
2664
|
/** What is under a client point: a time slot, a day, or a timeline row and date. Null outside the view. */
|
|
2528
2665
|
hitTest(clientX, clientY) {
|
|
@@ -2600,7 +2737,8 @@ var Calendar = class {
|
|
|
2600
2737
|
out.push(...classList(this.opts.dayClassNames?.(this.dayArg(day))));
|
|
2601
2738
|
return out;
|
|
2602
2739
|
}
|
|
2603
|
-
isEditable(n, what) {
|
|
2740
|
+
isEditable(n, what, presentation = false) {
|
|
2741
|
+
if (!presentation && this.itemWriteStates.get(n.id) === "pending") return false;
|
|
2604
2742
|
if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
|
|
2605
2743
|
if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
|
|
2606
2744
|
const it = n.item;
|
|
@@ -2640,13 +2778,14 @@ var Calendar = class {
|
|
|
2640
2778
|
if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
|
|
2641
2779
|
if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
|
|
2642
2780
|
const indicator = variant === "indicator";
|
|
2643
|
-
if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
|
|
2781
|
+
if (!preview && !indicator && (this.isEditable(n, "start", true) || this.isEditable(n, "duration", true))) el.classList.add("is-draggable");
|
|
2644
2782
|
if (this.draggingId === n.id) el.classList.add("is-dragging");
|
|
2645
2783
|
if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
|
|
2646
2784
|
el.classList.add(...classList(it.className));
|
|
2647
2785
|
const arg = this.itemArg(el, meta);
|
|
2648
2786
|
el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
|
|
2649
2787
|
el.dataset.id = n.id;
|
|
2788
|
+
this.applyItemWriteState(el, n.id);
|
|
2650
2789
|
if (it.kind) el.dataset.kind = it.kind;
|
|
2651
2790
|
const color = it.color ?? it.borderColor;
|
|
2652
2791
|
if (color) el.style.setProperty("--tnc-c", color);
|
|
@@ -2663,6 +2802,7 @@ var Calendar = class {
|
|
|
2663
2802
|
const custom = variant === "list" ? this.opts.listItemContent?.(arg) : null;
|
|
2664
2803
|
const content = indicator ? null : custom ?? this.opts.itemContent?.(arg);
|
|
2665
2804
|
if (indicator) {
|
|
2805
|
+
if (this.opts.monthDisplay === "chips") el.append(h(this.doc, "span", "tnc-item-title", null, it.title));
|
|
2666
2806
|
} else if (content != null) {
|
|
2667
2807
|
const wrap = h(this.doc, "div", "tnc-item-main");
|
|
2668
2808
|
wrap.append(content);
|
|
@@ -2675,6 +2815,7 @@ var Calendar = class {
|
|
|
2675
2815
|
return el;
|
|
2676
2816
|
}
|
|
2677
2817
|
refreshItemEl(el, meta) {
|
|
2818
|
+
this.applyItemWriteState(el, meta.n.id);
|
|
2678
2819
|
const time = el.querySelector(":scope .tnc-item-time");
|
|
2679
2820
|
if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
|
|
2680
2821
|
el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
|
|
@@ -2697,9 +2838,10 @@ var Calendar = class {
|
|
|
2697
2838
|
if (meta) this.opts.itemWillUnmount?.(this.itemArg(el, meta));
|
|
2698
2839
|
el.remove();
|
|
2699
2840
|
}
|
|
2700
|
-
openMore(day, anchor, jsEvent) {
|
|
2841
|
+
openMore(day, anchor, jsEvent, itemIds) {
|
|
2701
2842
|
jsEvent.stopPropagation();
|
|
2702
|
-
const
|
|
2843
|
+
const group = itemIds ? new Set(itemIds) : null;
|
|
2844
|
+
const items = this.store.inRange(day, day + 1).filter((item) => !group || group.has(item.id)).filter((n) => n.item.display !== "background" && (n.allDay || anchor.classList.contains("tnc-more--stack") || this.opts.view === "month" || !this.opts.timeGrid || this.opts.eventMaxStack > 0)).sort((a, b) => Number(b.allDay) - Number(a.allDay) || this.order(a, b));
|
|
2703
2845
|
const shown = new Set(
|
|
2704
2846
|
[...this.viewHost.querySelectorAll(".tnc-item")].filter((el2) => this.meta.get(el2)?.day === day || (this.meta.get(el2)?.n.startDay ?? Infinity) <= day).map((el2) => el2.dataset.id ?? "")
|
|
2705
2847
|
);
|
|
@@ -2728,15 +2870,7 @@ var Calendar = class {
|
|
|
2728
2870
|
}
|
|
2729
2871
|
pool.end();
|
|
2730
2872
|
this.popover = { el, pool, anchor, day };
|
|
2731
|
-
|
|
2732
|
-
const ar = anchor.getBoundingClientRect();
|
|
2733
|
-
const w = Math.max(220, ar.width + 24);
|
|
2734
|
-
let left = ar.left - rr.left - 12;
|
|
2735
|
-
left = Math.max(4, Math.min(left, rr.width - w - 4));
|
|
2736
|
-
let top = ar.top - rr.top - 40;
|
|
2737
|
-
const height = el.offsetHeight;
|
|
2738
|
-
top = Math.max(4, Math.min(top, rr.height - height - 4));
|
|
2739
|
-
setStyle(el, { left, top, width: w });
|
|
2873
|
+
this.positionMore();
|
|
2740
2874
|
el.focus();
|
|
2741
2875
|
}
|
|
2742
2876
|
/* =============================================================== render */
|
|
@@ -2860,8 +2994,11 @@ var Calendar = class {
|
|
|
2860
2994
|
this.current.setScroll({ top: keep.top, left: 0 });
|
|
2861
2995
|
this.current.placeInstant(keep.ms, keep.vx);
|
|
2862
2996
|
} else if (scroll) this.current.setScroll(scroll);
|
|
2863
|
-
else if (this.current instanceof TimeGridView)
|
|
2864
|
-
|
|
2997
|
+
else if (this.current instanceof TimeGridView) {
|
|
2998
|
+
this.current.scrollToTime(this.slot.scroll);
|
|
2999
|
+
this.current.revealDay(this.anchor);
|
|
3000
|
+
} else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
3001
|
+
else if (this.current instanceof YearView) this.current.revealDay(this.anchor);
|
|
2865
3002
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
2866
3003
|
}
|
|
2867
3004
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -2887,9 +3024,11 @@ var Calendar = class {
|
|
|
2887
3024
|
}
|
|
2888
3025
|
/** The range, run on to the end of the strip's days so their dots are right. */
|
|
2889
3026
|
dataRangeFor(range) {
|
|
2890
|
-
const
|
|
2891
|
-
|
|
2892
|
-
|
|
3027
|
+
const window = this.strip?.window();
|
|
3028
|
+
const start = window ? Math.min(range.startDay, window.start) : range.startDay;
|
|
3029
|
+
const end = window ? Math.max(range.endDay, window.end) : range.endDay;
|
|
3030
|
+
if (start === range.startDay && end === range.endDay) return range;
|
|
3031
|
+
return { ...range, startDay: start, start: new Date(toInstant(start, 0, this.zone)), endDay: end, end: new Date(toInstant(end, 0, this.zone)) };
|
|
2893
3032
|
}
|
|
2894
3033
|
/** Adds, updates or removes the date strip for the current view. */
|
|
2895
3034
|
syncStrip(navigated) {
|
|
@@ -2936,20 +3075,24 @@ var Calendar = class {
|
|
|
2936
3075
|
stripShown() {
|
|
2937
3076
|
if (!this.strip || this.rebuilding || !this.view) return;
|
|
2938
3077
|
const next = this.dataRangeFor(this.view.range);
|
|
2939
|
-
if (next.endDay === this.view.dataRange.endDay) return;
|
|
3078
|
+
if (next.startDay === this.view.dataRange.startDay && next.endDay === this.view.dataRange.endDay) return;
|
|
2940
3079
|
this.view = { ...this.view, dataRange: next };
|
|
2941
3080
|
this.opts.onRangeChange?.(this.view);
|
|
2942
3081
|
}
|
|
2943
3082
|
/** What is under a point, the date strip included. */
|
|
2944
3083
|
hitAt(x, y) {
|
|
3084
|
+
const target = this.doc.elementFromPoint(x, y);
|
|
3085
|
+
if (!target || !this.root.contains(target)) {
|
|
3086
|
+
this.strip?.markTarget(null);
|
|
3087
|
+
return null;
|
|
3088
|
+
}
|
|
2945
3089
|
const strip = this.strip;
|
|
2946
3090
|
if (strip) {
|
|
2947
|
-
const
|
|
2948
|
-
const day = el ? strip.dayAt(el) : null;
|
|
3091
|
+
const day = strip.dayAt(target);
|
|
2949
3092
|
strip.markTarget(day !== null && this.draggingId ? day : null);
|
|
2950
3093
|
if (day !== null) return { kind: "day", day };
|
|
2951
3094
|
}
|
|
2952
|
-
return this.current?.hit(x, y)
|
|
3095
|
+
return this.current?.el.contains(target) ? this.current.hit(x, y) : null;
|
|
2953
3096
|
}
|
|
2954
3097
|
agendaList() {
|
|
2955
3098
|
return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
|
|
@@ -3032,17 +3175,44 @@ var Calendar = class {
|
|
|
3032
3175
|
);
|
|
3033
3176
|
const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
|
|
3034
3177
|
const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
|
|
3178
|
+
const viewSelect = h(this.doc, "select", "tnc-btn tnc-toolbar-view-select", {
|
|
3179
|
+
"aria-label": this.text.viewHints[this.opts.view] ?? this.text.views[this.opts.view] ?? this.opts.view
|
|
3180
|
+
});
|
|
3035
3181
|
for (const v of views) {
|
|
3036
3182
|
const label = v === "days" ? this.text.views.days ?? `${this.opts.dayCount} days` : this.text.views[v] ?? v;
|
|
3037
3183
|
viewGroup.append(btn(label, this.text.viewHints[v] ?? label, `tnc-btn-view tnc-btn-view-${v}`, () => this.changeView(v), v === this.opts.view));
|
|
3184
|
+
viewSelect.append(h(this.doc, "option", "", { value: v }, label));
|
|
3038
3185
|
}
|
|
3186
|
+
viewSelect.value = this.opts.view;
|
|
3187
|
+
viewSelect.addEventListener("change", () => this.changeView(viewSelect.value));
|
|
3039
3188
|
const start = h(this.doc, "div", "tnc-toolbar-start");
|
|
3040
3189
|
const end = h(this.doc, "div", "tnc-toolbar-end");
|
|
3041
3190
|
if (cfg.extraPlacement === "start") start.append(...extras);
|
|
3042
3191
|
start.append(nav, title);
|
|
3043
|
-
end.append(viewGroup);
|
|
3192
|
+
end.append(viewGroup, viewSelect);
|
|
3044
3193
|
if (cfg.extraPlacement !== "start") end.append(...extras);
|
|
3045
3194
|
this.toolbarEl.append(start, end);
|
|
3195
|
+
this.syncToolbarViewDisplay();
|
|
3196
|
+
}
|
|
3197
|
+
syncToolbarViewDisplay() {
|
|
3198
|
+
const buttons = this.toolbarEl.querySelector(".tnc-toolbar-views");
|
|
3199
|
+
const select = this.toolbarEl.querySelector(".tnc-toolbar-view-select");
|
|
3200
|
+
const start = this.toolbarEl.querySelector(".tnc-toolbar-start");
|
|
3201
|
+
if (!buttons || !select || !start) return;
|
|
3202
|
+
const config = this.opts.toolbar;
|
|
3203
|
+
const mode = typeof config === "object" ? config.viewDisplay ?? "auto" : "auto";
|
|
3204
|
+
buttons.hidden = false;
|
|
3205
|
+
select.hidden = true;
|
|
3206
|
+
buttons.style.display = "flex";
|
|
3207
|
+
select.style.display = "none";
|
|
3208
|
+
const startWidth = Array.from(start.children).reduce((width, child) => width + child.scrollWidth, 0);
|
|
3209
|
+
const availableWidth = Math.min(this.root.clientWidth, this.toolbarEl.clientWidth);
|
|
3210
|
+
const compact = mode === "select" || mode === "auto" && startWidth + buttons.scrollWidth + 32 > availableWidth;
|
|
3211
|
+
buttons.hidden = compact;
|
|
3212
|
+
select.hidden = !compact;
|
|
3213
|
+
buttons.style.display = compact ? "none" : "flex";
|
|
3214
|
+
select.style.display = compact ? "" : "none";
|
|
3215
|
+
select.parentElement?.classList.toggle("tnc-toolbar-end--compact", compact);
|
|
3046
3216
|
}
|
|
3047
3217
|
applyChange(change) {
|
|
3048
3218
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3107,7 +3277,7 @@ var Calendar = class {
|
|
|
3107
3277
|
const s = wallTime(n.startMs, this.zone);
|
|
3108
3278
|
const start = this.fmt.time(s.minute, true);
|
|
3109
3279
|
if (variant === "dot" || variant === "bar" || variant === "indicator") return { start: s.day === day ? start : "", end: "" };
|
|
3110
|
-
if (n.startMs === n.endMs) return { start, end: "" };
|
|
3280
|
+
if (n.startMs === n.endMs || n.item.timeDisplay === "start") return { start, end: "" };
|
|
3111
3281
|
return { start, end: this.fmt.time(wallTime(n.endMs, this.zone).minute, true) };
|
|
3112
3282
|
}
|
|
3113
3283
|
fillTime(el, n, variant, day) {
|
|
@@ -3123,7 +3293,8 @@ var Calendar = class {
|
|
|
3123
3293
|
}
|
|
3124
3294
|
const s = wallTime(n.startMs, this.zone);
|
|
3125
3295
|
const e = wallTime(n.endMs, this.zone);
|
|
3126
|
-
|
|
3296
|
+
const start = `${n.item.title}, ${this.fmt.longDate(s.day)} ${this.fmt.time(s.minute)}`;
|
|
3297
|
+
return n.item.timeDisplay === "start" || n.startMs === n.endMs ? start : `${start} \u2013 ${this.fmt.time(e.minute)}`;
|
|
3127
3298
|
}
|
|
3128
3299
|
defaultContent(meta) {
|
|
3129
3300
|
const { n, variant, day } = meta;
|
|
@@ -3151,7 +3322,7 @@ var Calendar = class {
|
|
|
3151
3322
|
addHandles(el, meta) {
|
|
3152
3323
|
const { n, variant } = meta;
|
|
3153
3324
|
const ports = variant === "timeline" && !meta.mirror && n.id !== EXTERNAL_ID && !!this.opts.timeline?.onLink;
|
|
3154
|
-
if (variant === "dot" || !this.isEditable(n, "duration")) {
|
|
3325
|
+
if (variant === "dot" || !this.isEditable(n, "duration", true)) {
|
|
3155
3326
|
if (ports) this.addPorts(el, n);
|
|
3156
3327
|
return;
|
|
3157
3328
|
}
|
|
@@ -3175,6 +3346,38 @@ var Calendar = class {
|
|
|
3175
3346
|
}
|
|
3176
3347
|
}
|
|
3177
3348
|
/* ============================================================== popover */
|
|
3349
|
+
onMoreGeometryChange = () => this.positionMore();
|
|
3350
|
+
listenMoreGeometry(on) {
|
|
3351
|
+
const method = on ? "addEventListener" : "removeEventListener";
|
|
3352
|
+
this.doc[method]("scroll", this.onMoreGeometryChange, true);
|
|
3353
|
+
this.win[method]("resize", this.onMoreGeometryChange);
|
|
3354
|
+
this.win.visualViewport?.[method]("resize", this.onMoreGeometryChange);
|
|
3355
|
+
this.win.visualViewport?.[method]("scroll", this.onMoreGeometryChange);
|
|
3356
|
+
}
|
|
3357
|
+
positionMore() {
|
|
3358
|
+
const popover = this.popover;
|
|
3359
|
+
if (!popover) return;
|
|
3360
|
+
if (!popover.anchor.isConnected) {
|
|
3361
|
+
this.closeMore();
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
const viewport = this.win.visualViewport;
|
|
3365
|
+
const root = this.root.getBoundingClientRect();
|
|
3366
|
+
const anchor = popover.anchor.getBoundingClientRect();
|
|
3367
|
+
const leftEdge = Math.max(root.left, viewport?.offsetLeft ?? 0) + 4;
|
|
3368
|
+
const topEdge = Math.max(root.top, viewport?.offsetTop ?? 0) + 4;
|
|
3369
|
+
const rightEdge = Math.min(root.right, (viewport?.offsetLeft ?? 0) + (viewport?.width ?? this.win.innerWidth)) - 4;
|
|
3370
|
+
const bottomEdge = Math.min(root.bottom, (viewport?.offsetTop ?? 0) + (viewport?.height ?? this.win.innerHeight)) - 4;
|
|
3371
|
+
if (rightEdge <= leftEdge || bottomEdge <= topEdge) {
|
|
3372
|
+
this.closeMore();
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
const width = Math.min(360, rightEdge - leftEdge);
|
|
3376
|
+
setStyle(popover.el, { width, "max-height": Math.min(420, bottomEdge - topEdge) });
|
|
3377
|
+
const left = Math.max(leftEdge, Math.min(anchor.left - 12, rightEdge - width));
|
|
3378
|
+
const top = Math.max(topEdge, Math.min(anchor.top - 40, bottomEdge - popover.el.offsetHeight));
|
|
3379
|
+
setStyle(popover.el, { left: left - root.left, top: top - root.top });
|
|
3380
|
+
}
|
|
3178
3381
|
refreshMore() {
|
|
3179
3382
|
const p = this.popover;
|
|
3180
3383
|
if (!p) return;
|
|
@@ -3184,6 +3387,7 @@ var Calendar = class {
|
|
|
3184
3387
|
p.pool.place(n.id, { n, day: p.day, isStart: n.startDay >= p.day, isEnd: n.endDay <= p.day + 1, variant: n.allDay ? "bar" : "dot", mirror: false });
|
|
3185
3388
|
}
|
|
3186
3389
|
p.pool.end();
|
|
3390
|
+
this.positionMore();
|
|
3187
3391
|
}
|
|
3188
3392
|
closeMore(restoreFocus = false) {
|
|
3189
3393
|
const p = this.popover;
|
|
@@ -3211,6 +3415,7 @@ var Calendar = class {
|
|
|
3211
3415
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
3212
3416
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
3213
3417
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3418
|
+
this.listenMoreGeometry(true);
|
|
3214
3419
|
}
|
|
3215
3420
|
unbindEvents() {
|
|
3216
3421
|
const r = this.root;
|
|
@@ -3229,6 +3434,7 @@ var Calendar = class {
|
|
|
3229
3434
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
3230
3435
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
3231
3436
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3437
|
+
this.listenMoreGeometry(false);
|
|
3232
3438
|
this.listenDoc(false);
|
|
3233
3439
|
}
|
|
3234
3440
|
listenDoc(on) {
|
|
@@ -3244,6 +3450,10 @@ var Calendar = class {
|
|
|
3244
3450
|
return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
|
|
3245
3451
|
}
|
|
3246
3452
|
onDocPointerDown = (ev) => {
|
|
3453
|
+
if (this.cancelledPointerId !== null) {
|
|
3454
|
+
this.cancelledPointerId = null;
|
|
3455
|
+
this.listenDoc(false);
|
|
3456
|
+
}
|
|
3247
3457
|
const t = ev.target;
|
|
3248
3458
|
if (this.popover && !this.popover.el.contains(t) && !this.popover.anchor.contains(t)) this.closeMore();
|
|
3249
3459
|
if (this.selection && !this.root.contains(t)) this.unselect();
|
|
@@ -3251,6 +3461,7 @@ var Calendar = class {
|
|
|
3251
3461
|
onPointerDown = (ev) => {
|
|
3252
3462
|
if (ev.button !== 0 || this.session) return;
|
|
3253
3463
|
const target = ev.target;
|
|
3464
|
+
if (target.closest(".tnc-dg--compact .tnc-dg-day")) return;
|
|
3254
3465
|
if (target.closest("button, a, input, select, textarea, .tnc-toolbar, .tnc-port, [data-tnc-no-drag]")) {
|
|
3255
3466
|
if (!target.closest(".tnc-item")) return;
|
|
3256
3467
|
if (target.closest(".tnc-port, [data-tnc-no-drag], input, select, textarea")) return;
|
|
@@ -3274,6 +3485,7 @@ var Calendar = class {
|
|
|
3274
3485
|
mode = "select";
|
|
3275
3486
|
}
|
|
3276
3487
|
const touch = ev.pointerType === "touch";
|
|
3488
|
+
const overflowOrigin = itemEl?.closest(".tnc-popover") && origin ? wallTime(origin.startMs, this.zone) : null;
|
|
3277
3489
|
const s = {
|
|
3278
3490
|
mode,
|
|
3279
3491
|
pointerId: ev.pointerId,
|
|
@@ -3282,7 +3494,7 @@ var Calendar = class {
|
|
|
3282
3494
|
y0: ev.clientY,
|
|
3283
3495
|
active: false,
|
|
3284
3496
|
origin,
|
|
3285
|
-
grab: this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
3497
|
+
grab: overflowOrigin ? this.current instanceof TimeGridView && !origin?.allDay ? { kind: "time", day: overflowOrigin.day, minute: overflowOrigin.minute } : { kind: "day", day: this.meta.get(itemEl).day } : this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
3286
3498
|
proposal: null,
|
|
3287
3499
|
denied: false,
|
|
3288
3500
|
timer: null,
|
|
@@ -3307,16 +3519,79 @@ var Calendar = class {
|
|
|
3307
3519
|
};
|
|
3308
3520
|
activate(s) {
|
|
3309
3521
|
s.active = true;
|
|
3522
|
+
if (s.lastEvent) this.leaveHover(s.lastEvent);
|
|
3310
3523
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
3311
3524
|
s.timer = null;
|
|
3312
3525
|
if (s.origin) {
|
|
3313
3526
|
this.draggingId = s.origin.id;
|
|
3314
|
-
for (const el of this.elementsFor(s.origin.id))
|
|
3527
|
+
for (const el of this.elementsFor(s.origin.id)) {
|
|
3528
|
+
if (this.opts.view !== "list" && s.mode === "move") {
|
|
3529
|
+
const source = el.cloneNode(true);
|
|
3530
|
+
source.classList.remove("is-selected");
|
|
3531
|
+
source.classList.add("tnc-drag-source");
|
|
3532
|
+
source.setAttribute("aria-hidden", "true");
|
|
3533
|
+
source.inert = true;
|
|
3534
|
+
for (const node of [source, ...source.querySelectorAll("[id], [data-id], [data-task-path]")]) {
|
|
3535
|
+
node.removeAttribute("id");
|
|
3536
|
+
node.removeAttribute("data-id");
|
|
3537
|
+
node.removeAttribute("data-task-path");
|
|
3538
|
+
}
|
|
3539
|
+
el.before(source);
|
|
3540
|
+
this.dragSources.push(source);
|
|
3541
|
+
}
|
|
3542
|
+
if (this.opts.view === "list" && !this.listDragPreview && s.mode === "move") {
|
|
3543
|
+
const bounds = el.getBoundingClientRect();
|
|
3544
|
+
const preview = el.cloneNode(true);
|
|
3545
|
+
preview.classList.add("tnc-drag-preview", "is-dragging");
|
|
3546
|
+
preview.removeAttribute("data-id");
|
|
3547
|
+
preview.removeAttribute("data-task-path");
|
|
3548
|
+
preview.setAttribute("aria-hidden", "true");
|
|
3549
|
+
preview.inert = true;
|
|
3550
|
+
preview.style.width = `${bounds.width}px`;
|
|
3551
|
+
this.root.append(preview);
|
|
3552
|
+
this.listDragPreview = { element: preview, offsetX: s.x0 - bounds.left, offsetY: s.y0 - bounds.top, width: bounds.width, height: bounds.height };
|
|
3553
|
+
}
|
|
3554
|
+
el.classList.add("is-dragging");
|
|
3555
|
+
}
|
|
3315
3556
|
}
|
|
3316
3557
|
this.closeMore();
|
|
3317
3558
|
if (s.mode !== "select") this.unselectSilently();
|
|
3318
3559
|
this.root.classList.add("tnc-dragging", `tnc-dragging-${s.mode}`);
|
|
3319
3560
|
if (s.lastEvent) this.track(s, s.lastEvent);
|
|
3561
|
+
this.startDragScroll();
|
|
3562
|
+
}
|
|
3563
|
+
startDragScroll() {
|
|
3564
|
+
if (this.dragScrollFrame !== null) return;
|
|
3565
|
+
const tick = () => {
|
|
3566
|
+
this.dragScrollFrame = null;
|
|
3567
|
+
const session = this.session;
|
|
3568
|
+
const pointer = session?.lastEvent;
|
|
3569
|
+
if (!session?.active || !pointer) return;
|
|
3570
|
+
const origin = session.origin ? this.elementsFor(session.origin.id)[0] : this.viewHost;
|
|
3571
|
+
const underPointer = this.doc.elementFromPoint(pointer.clientX, pointer.clientY);
|
|
3572
|
+
let moved = false;
|
|
3573
|
+
if (origin && underPointer && this.root.contains(underPointer)) {
|
|
3574
|
+
for (const axis of ["x", "y"]) {
|
|
3575
|
+
const scroller = scrollerOf(origin, axis);
|
|
3576
|
+
if (!scroller) continue;
|
|
3577
|
+
const viewport = scroller === this.doc.scrollingElement ? { left: 0, top: 0, right: this.win.innerWidth, bottom: this.win.innerHeight } : scroller.getBoundingClientRect();
|
|
3578
|
+
const coordinate = axis === "x" ? pointer.clientX : pointer.clientY;
|
|
3579
|
+
const lower = Math.max(0, axis === "x" ? viewport.left : viewport.top);
|
|
3580
|
+
const upper = Math.min(axis === "x" ? this.win.innerWidth : this.win.innerHeight, axis === "x" ? viewport.right : viewport.bottom);
|
|
3581
|
+
if (coordinate < lower || coordinate > upper) continue;
|
|
3582
|
+
const delta = coordinate < lower + 32 ? -Math.min(12, (lower + 32 - coordinate) / 2) : coordinate > upper - 32 ? Math.min(12, (coordinate - upper + 32) / 2) : 0;
|
|
3583
|
+
if (!delta) continue;
|
|
3584
|
+
if (!this.dragScrollOrigins.has(scroller)) this.dragScrollOrigins.set(scroller, { top: scroller.scrollTop, left: scroller.scrollLeft });
|
|
3585
|
+
const before = axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
|
|
3586
|
+
if (axis === "x") scroller.scrollLeft += delta;
|
|
3587
|
+
else scroller.scrollTop += delta;
|
|
3588
|
+
moved ||= before !== (axis === "x" ? scroller.scrollLeft : scroller.scrollTop);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
if (moved) this.track(session, pointer);
|
|
3592
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
3593
|
+
};
|
|
3594
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
3320
3595
|
}
|
|
3321
3596
|
onTouchMove = (ev) => {
|
|
3322
3597
|
if (this.session?.active) ev.preventDefault();
|
|
@@ -3346,6 +3621,12 @@ var Calendar = class {
|
|
|
3346
3621
|
this.track(s, ev);
|
|
3347
3622
|
};
|
|
3348
3623
|
onPointerUp = (ev) => {
|
|
3624
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
3625
|
+
this.cancelledPointerId = null;
|
|
3626
|
+
this.suppressNextClick();
|
|
3627
|
+
this.listenDoc(false);
|
|
3628
|
+
return;
|
|
3629
|
+
}
|
|
3349
3630
|
const s = this.session;
|
|
3350
3631
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
3351
3632
|
if (!s.active) {
|
|
@@ -3358,28 +3639,49 @@ var Calendar = class {
|
|
|
3358
3639
|
return;
|
|
3359
3640
|
}
|
|
3360
3641
|
this.suppressNextClick();
|
|
3642
|
+
this.track(s, ev);
|
|
3361
3643
|
this.endSession(ev, false);
|
|
3362
3644
|
};
|
|
3363
3645
|
onPointerCancel = (ev) => {
|
|
3646
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
3647
|
+
this.cancelledPointerId = null;
|
|
3648
|
+
this.listenDoc(false);
|
|
3649
|
+
}
|
|
3364
3650
|
if (this.session && ev.pointerId === this.session.pointerId) this.endSession(null, true);
|
|
3365
3651
|
};
|
|
3366
3652
|
onDocKeyDown = (ev) => {
|
|
3367
3653
|
if (ev.key === "Escape" && this.session) {
|
|
3368
3654
|
ev.preventDefault();
|
|
3369
3655
|
ev.stopPropagation();
|
|
3656
|
+
const pointerId = this.session.active ? this.session.pointerId : null;
|
|
3370
3657
|
this.endSession(null, true);
|
|
3658
|
+
if (pointerId !== null) {
|
|
3659
|
+
this.cancelledPointerId = pointerId;
|
|
3660
|
+
this.listenDoc(true);
|
|
3661
|
+
}
|
|
3371
3662
|
}
|
|
3372
3663
|
};
|
|
3373
3664
|
/** Proposal for the current pointer position. */
|
|
3374
3665
|
track(s, ev) {
|
|
3666
|
+
if (this.listDragPreview) {
|
|
3667
|
+
const preview = this.listDragPreview;
|
|
3668
|
+
const bounds = this.root.getBoundingClientRect();
|
|
3669
|
+
preview.element.style.left = `${Math.max(8, Math.min(this.win.innerWidth - preview.width - 8, ev.clientX - preview.offsetX)) - bounds.left}px`;
|
|
3670
|
+
preview.element.style.top = `${Math.max(8, Math.min(this.win.innerHeight - preview.height - 8, ev.clientY - preview.offsetY)) - bounds.top}px`;
|
|
3671
|
+
}
|
|
3375
3672
|
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
3376
3673
|
if (s.mode === "external") {
|
|
3377
3674
|
this.trackExternal(s, hit);
|
|
3378
3675
|
return;
|
|
3379
3676
|
}
|
|
3380
3677
|
if (!s.grab) s.grab = hit;
|
|
3381
|
-
if (!hit || !s.grab)
|
|
3678
|
+
if (!hit || !s.grab) {
|
|
3679
|
+
this.invalidateProposal(s);
|
|
3680
|
+
return;
|
|
3681
|
+
}
|
|
3382
3682
|
if (s.mode === "select") {
|
|
3683
|
+
s.denied = false;
|
|
3684
|
+
this.root.classList.remove("tnc-drop-denied");
|
|
3383
3685
|
const sel = this.selectionFor(s.grab, hit);
|
|
3384
3686
|
if (!sel) return;
|
|
3385
3687
|
this.selection = sel;
|
|
@@ -3388,25 +3690,40 @@ var Calendar = class {
|
|
|
3388
3690
|
}
|
|
3389
3691
|
const origin = s.origin;
|
|
3390
3692
|
const p = s.mode === "move" ? this.moveProposal(origin, s.grab, hit) : this.resizeProposal(origin, hit, s.mode === "resize-start");
|
|
3391
|
-
if (!p)
|
|
3693
|
+
if (!p) {
|
|
3694
|
+
this.invalidateProposal(s);
|
|
3695
|
+
return;
|
|
3696
|
+
}
|
|
3392
3697
|
const allowed = this.opts.canDrop?.({ item: origin.item, start: new Date(p.startMs), end: new Date(p.endMs), allDay: p.allDay, rowId: p.rowId }) ?? true;
|
|
3393
3698
|
s.denied = !allowed;
|
|
3394
3699
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
3395
|
-
if (!allowed
|
|
3700
|
+
if (!allowed) {
|
|
3701
|
+
this.invalidateProposal(s);
|
|
3702
|
+
return;
|
|
3703
|
+
}
|
|
3704
|
+
if (sameProposal(p, s.proposal)) return;
|
|
3396
3705
|
s.proposal = p;
|
|
3397
3706
|
this.dragRow = p.rowId !== void 0 ? { id: origin.id, rowId: p.rowId } : null;
|
|
3398
|
-
this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
3707
|
+
if (this.opts.view !== "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
3708
|
+
}
|
|
3709
|
+
invalidateProposal(s) {
|
|
3710
|
+
const hadProposal = s.proposal !== null;
|
|
3711
|
+
s.proposal = null;
|
|
3712
|
+
s.hit = null;
|
|
3713
|
+
s.denied = true;
|
|
3714
|
+
this.dragRow = null;
|
|
3715
|
+
this.root.classList.add("tnc-drop-denied");
|
|
3716
|
+
this.strip?.markTarget(null);
|
|
3717
|
+
this.unselectSilently();
|
|
3718
|
+
if (s.mode === "external") this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
3719
|
+
else if (hadProposal && s.origin) this.applyChange(this.store.upsert([s.origin.item]));
|
|
3399
3720
|
}
|
|
3400
3721
|
trackExternal(s, raw) {
|
|
3401
3722
|
const src = s.source;
|
|
3402
3723
|
const hit = raw?.kind === "timeline" && raw.area !== "track" && raw.area !== "header" ? null : raw;
|
|
3403
3724
|
s.hit = hit;
|
|
3404
3725
|
if (!hit) {
|
|
3405
|
-
|
|
3406
|
-
s.proposal = null;
|
|
3407
|
-
this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
3408
|
-
}
|
|
3409
|
-
this.unselectSilently();
|
|
3726
|
+
this.invalidateProposal(s);
|
|
3410
3727
|
return;
|
|
3411
3728
|
}
|
|
3412
3729
|
const dur = src.durationMinutes ?? this.opts.defaultTimedMinutes;
|
|
@@ -3425,6 +3742,10 @@ var Calendar = class {
|
|
|
3425
3742
|
const allowed = this.opts.canDrop?.({ item: this.externalItem(src, p), start: new Date(p.startMs), end: new Date(p.endMs), allDay: p.allDay, rowId: p.rowId }) ?? true;
|
|
3426
3743
|
s.denied = !allowed;
|
|
3427
3744
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
3745
|
+
if (!allowed) {
|
|
3746
|
+
this.invalidateProposal(s);
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3428
3749
|
if (sameProposal(p, s.proposal)) return;
|
|
3429
3750
|
s.proposal = p;
|
|
3430
3751
|
if (hit.kind === "timeline" && p.rowId === void 0) {
|
|
@@ -3617,7 +3938,18 @@ var Calendar = class {
|
|
|
3617
3938
|
endSession(ev, cancel) {
|
|
3618
3939
|
const s = this.session;
|
|
3619
3940
|
if (!s) return;
|
|
3941
|
+
if (this.dragScrollFrame !== null) this.win.cancelAnimationFrame(this.dragScrollFrame);
|
|
3942
|
+
this.dragScrollFrame = null;
|
|
3943
|
+
if (cancel || s.denied) for (const [scroller, position] of this.dragScrollOrigins) {
|
|
3944
|
+
scroller.scrollTop = position.top;
|
|
3945
|
+
scroller.scrollLeft = position.left;
|
|
3946
|
+
}
|
|
3947
|
+
this.dragScrollOrigins.clear();
|
|
3620
3948
|
this.session = null;
|
|
3949
|
+
this.listDragPreview?.element.remove();
|
|
3950
|
+
this.listDragPreview = null;
|
|
3951
|
+
for (const source of this.dragSources) source.remove();
|
|
3952
|
+
this.dragSources = [];
|
|
3621
3953
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
3622
3954
|
this.listenDoc(false);
|
|
3623
3955
|
this.root.classList.remove(
|
|
@@ -3686,10 +4018,16 @@ var Calendar = class {
|
|
|
3686
4018
|
revert();
|
|
3687
4019
|
return;
|
|
3688
4020
|
}
|
|
4021
|
+
if (this.opts.view === "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
3689
4022
|
this.flush();
|
|
3690
4023
|
this.commit(s.mode === "move" ? "drop" : "resize", origin, p, ev, revert);
|
|
3691
4024
|
}
|
|
3692
4025
|
commit(kind, origin, p, ev, revert) {
|
|
4026
|
+
let reverted = false;
|
|
4027
|
+
const mark = (state) => {
|
|
4028
|
+
this.itemWriteStates.set(origin.id, state);
|
|
4029
|
+
for (const element of this.elementsFor(origin.id)) this.applyItemWriteState(element, origin.id);
|
|
4030
|
+
};
|
|
3693
4031
|
const arg = {
|
|
3694
4032
|
item: origin.item,
|
|
3695
4033
|
start: new Date(p.startMs),
|
|
@@ -3705,19 +4043,32 @@ var Calendar = class {
|
|
|
3705
4043
|
jsEvent: ev,
|
|
3706
4044
|
source: ev && "key" in ev ? "keyboard" : "pointer",
|
|
3707
4045
|
view: this.view,
|
|
3708
|
-
revert
|
|
4046
|
+
revert: () => {
|
|
4047
|
+
reverted = true;
|
|
4048
|
+
revert();
|
|
4049
|
+
mark("failed");
|
|
4050
|
+
}
|
|
3709
4051
|
};
|
|
3710
4052
|
const handler = kind === "drop" ? this.opts.onItemDrop : this.opts.onItemResize;
|
|
3711
4053
|
try {
|
|
4054
|
+
mark("pending");
|
|
3712
4055
|
const res = handler?.(arg);
|
|
3713
4056
|
if (res && typeof res.then === "function") {
|
|
3714
|
-
Promise.resolve(res).
|
|
3715
|
-
|
|
4057
|
+
void Promise.resolve(res).then(() => {
|
|
4058
|
+
if (!reverted) mark("changed");
|
|
4059
|
+
}, () => arg.revert());
|
|
4060
|
+
} else if (!reverted) mark("changed");
|
|
3716
4061
|
} catch (err) {
|
|
3717
|
-
revert();
|
|
4062
|
+
arg.revert();
|
|
3718
4063
|
throw err;
|
|
3719
4064
|
}
|
|
3720
4065
|
}
|
|
4066
|
+
applyItemWriteState(element, id) {
|
|
4067
|
+
const state = this.itemWriteStates.get(id);
|
|
4068
|
+
for (const name of ["pending", "failed", "changed"]) element.classList.toggle(`is-write-${name}`, state === name);
|
|
4069
|
+
if (state === "pending") element.setAttribute("aria-busy", "true");
|
|
4070
|
+
else element.removeAttribute("aria-busy");
|
|
4071
|
+
}
|
|
3721
4072
|
/* --------------------------------------------------------- click / keys */
|
|
3722
4073
|
/** Runs in the capture phase so the click ending a drag never reaches content inside the calendar, React portals included. */
|
|
3723
4074
|
onClickCapture = (ev) => {
|
|
@@ -3727,6 +4078,12 @@ var Calendar = class {
|
|
|
3727
4078
|
ev.preventDefault();
|
|
3728
4079
|
};
|
|
3729
4080
|
onClick = (ev) => {
|
|
4081
|
+
const compactDay = ev.target.closest?.(".tnc-dg--compact .tnc-dg-day");
|
|
4082
|
+
if (compactDay && this.root.contains(compactDay) && !ev.target.closest(".tnc-navlink")) {
|
|
4083
|
+
const day = parseDay(compactDay.dataset.date ?? "");
|
|
4084
|
+
if (day !== null) this.openMore(day, compactDay, ev);
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
3730
4087
|
const yearDay = ev.target.closest?.(".tnc-ym-day");
|
|
3731
4088
|
if (yearDay && this.root.contains(yearDay)) {
|
|
3732
4089
|
const day = parseDay(yearDay.dataset.date ?? "");
|
|
@@ -3785,6 +4142,25 @@ var Calendar = class {
|
|
|
3785
4142
|
if (el && meta) this.opts.onItemHover?.({ ...this.itemArg(el, meta), jsEvent: ev, entering: false });
|
|
3786
4143
|
}
|
|
3787
4144
|
onKeyDown = (ev) => {
|
|
4145
|
+
if (this.popover && !ev.altKey && !ev.ctrlKey && !ev.metaKey && ["ArrowDown", "ArrowUp", "Home", "End"].includes(ev.key)) {
|
|
4146
|
+
const rows = [...this.popover.el.querySelectorAll(".tnc-item")];
|
|
4147
|
+
const index = rows.indexOf(this.doc.activeElement);
|
|
4148
|
+
const next = ev.key === "Home" ? 0 : ev.key === "End" ? rows.length - 1 : ev.key === "ArrowDown" ? (index + 1) % rows.length : index <= 0 ? rows.length - 1 : index - 1;
|
|
4149
|
+
ev.preventDefault();
|
|
4150
|
+
ev.stopPropagation();
|
|
4151
|
+
rows[next]?.focus();
|
|
4152
|
+
rows[next]?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
4155
|
+
if (ev.key === "Tab" && this.popover) {
|
|
4156
|
+
const controls = [...this.popover.el.querySelectorAll('button, a[href], [tabindex="0"]')];
|
|
4157
|
+
const index = controls.indexOf(this.doc.activeElement);
|
|
4158
|
+
const next = ev.shiftKey ? index <= 0 ? controls.length - 1 : index - 1 : (index + 1) % controls.length;
|
|
4159
|
+
ev.preventDefault();
|
|
4160
|
+
ev.stopPropagation();
|
|
4161
|
+
controls[next]?.focus();
|
|
4162
|
+
return;
|
|
4163
|
+
}
|
|
3788
4164
|
if (ev.key === "Escape" && this.popover) {
|
|
3789
4165
|
ev.preventDefault();
|
|
3790
4166
|
ev.stopPropagation();
|
|
@@ -3796,7 +4172,8 @@ var Calendar = class {
|
|
|
3796
4172
|
const day = parseDay(target.dataset.date ?? "");
|
|
3797
4173
|
if (day !== null) {
|
|
3798
4174
|
ev.preventDefault();
|
|
3799
|
-
|
|
4175
|
+
if (target.closest(".tnc-dg--compact")) this.openMore(day, target, ev);
|
|
4176
|
+
else this.clickSlot({ kind: "day", day }, ev, true);
|
|
3800
4177
|
}
|
|
3801
4178
|
return;
|
|
3802
4179
|
}
|
|
@@ -3884,9 +4261,13 @@ var Calendar = class {
|
|
|
3884
4261
|
const dt = ev.dataTransfer;
|
|
3885
4262
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
3886
4263
|
ev.preventDefault();
|
|
4264
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4265
|
+
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") {
|
|
4266
|
+
dt.dropEffect = "none";
|
|
4267
|
+
this.unselectSilently();
|
|
4268
|
+
return;
|
|
4269
|
+
}
|
|
3887
4270
|
dt.dropEffect = "copy";
|
|
3888
|
-
const hit = this.current.hit(ev.clientX, ev.clientY);
|
|
3889
|
-
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
3890
4271
|
const sel = this.selectionFor(hit, hit);
|
|
3891
4272
|
if (!sel) return;
|
|
3892
4273
|
this.selection = sel;
|
|
@@ -3900,7 +4281,7 @@ var Calendar = class {
|
|
|
3900
4281
|
const dt = ev.dataTransfer;
|
|
3901
4282
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
3902
4283
|
ev.preventDefault();
|
|
3903
|
-
const hit = this.
|
|
4284
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
3904
4285
|
this.unselectSilently();
|
|
3905
4286
|
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
3906
4287
|
const sel = this.selectionFor(hit, hit);
|
|
@@ -3931,7 +4312,7 @@ function scrollerOf(el, axis) {
|
|
|
3931
4312
|
return doc.scrollingElement;
|
|
3932
4313
|
}
|
|
3933
4314
|
function rangeMoved(prev, next) {
|
|
3934
|
-
return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.endDay !== next.dataRange.endDay;
|
|
4315
|
+
return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.startDay !== next.dataRange.startDay || prev.dataRange.endDay !== next.dataRange.endDay;
|
|
3935
4316
|
}
|
|
3936
4317
|
|
|
3937
4318
|
export {
|