@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
package/dist/cjs/react.cjs
CHANGED
|
@@ -696,6 +696,7 @@ var DEFAULT_TEXT = {
|
|
|
696
696
|
allDay: "All day",
|
|
697
697
|
noItems: "Nothing scheduled",
|
|
698
698
|
more: (n) => `+${n} more`,
|
|
699
|
+
appointments: (n) => `${n} appointments`,
|
|
699
700
|
itemCount: (n) => n === 1 ? "1 item" : `${n} items`,
|
|
700
701
|
weekNumber: (n) => `W${n}`,
|
|
701
702
|
views: { week: "Week", day: "Day", days: "Days", month: "Month", list: "List", year: "Year", timeline: "Timeline" },
|
|
@@ -765,6 +766,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
765
766
|
"dateStrip",
|
|
766
767
|
"listLeadTitle",
|
|
767
768
|
"listAddText",
|
|
769
|
+
"listEmptyContent",
|
|
768
770
|
"monthDisplay",
|
|
769
771
|
"timeZone",
|
|
770
772
|
"locale",
|
|
@@ -980,8 +982,9 @@ var DayGridView = class {
|
|
|
980
982
|
constructor(ctx) {
|
|
981
983
|
this.ctx = ctx;
|
|
982
984
|
const { doc, opts, days } = ctx;
|
|
983
|
-
this.dotsMode = opts.monthDisplay === "dots";
|
|
985
|
+
this.dotsMode = opts.monthDisplay === "dots" || opts.monthDisplay === "chips";
|
|
984
986
|
this.el = h(doc, "div", this.dotsMode ? "tnc-dg tnc-dg--dots" : "tnc-dg");
|
|
987
|
+
this.el.classList.toggle("tnc-dg--chips", opts.monthDisplay === "chips");
|
|
985
988
|
this.scroller = h(doc, "div", "tnc-dg-scroll");
|
|
986
989
|
this.grid = h(doc, "div", "tnc-dg-grid", { role: "grid", "aria-label": ctx.view.title });
|
|
987
990
|
const perWeek = this.daysPerWeek();
|
|
@@ -1011,14 +1014,14 @@ var DayGridView = class {
|
|
|
1011
1014
|
const cell = h(doc, "div", cls.join(" "), { role: "gridcell", "aria-label": ctx.fmt.longDate(d), "data-date": arg.dateStr });
|
|
1012
1015
|
if (this.dotsMode) cell.tabIndex = 0;
|
|
1013
1016
|
const num = ctx.fmt.dayNumber(d);
|
|
1014
|
-
const label = ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num}` : num;
|
|
1017
|
+
const label = !this.dotsMode && ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num}` : num;
|
|
1015
1018
|
cell.append(
|
|
1016
1019
|
h(
|
|
1017
1020
|
doc,
|
|
1018
1021
|
"div",
|
|
1019
1022
|
"tnc-dg-top",
|
|
1020
1023
|
null,
|
|
1021
|
-
opts.navLinks ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
1024
|
+
opts.navLinks && !this.dotsMode ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
1022
1025
|
)
|
|
1023
1026
|
);
|
|
1024
1027
|
dates.append(cell);
|
|
@@ -1032,20 +1035,20 @@ var DayGridView = class {
|
|
|
1032
1035
|
layer.append(content);
|
|
1033
1036
|
row.append(dates, layer);
|
|
1034
1037
|
this.grid.append(row);
|
|
1035
|
-
const dots =
|
|
1038
|
+
const dots = cells.map((cell) => {
|
|
1036
1039
|
const box = h(doc, "div", "tnc-dg-dots", { "aria-hidden": "true" });
|
|
1037
1040
|
if (opts.dayMaxEvents === false) box.classList.add("is-unlimited");
|
|
1038
1041
|
const count = h(doc, "span", "tnc-dg-dots-more");
|
|
1039
1042
|
cell.append(box);
|
|
1040
1043
|
return { box, pool: new ItemPool(ctx, box), count };
|
|
1041
|
-
})
|
|
1044
|
+
});
|
|
1042
1045
|
this.weeks.push({ days: wdays, row, cells, layer, sel, more, pool: new ItemPool(ctx, content), dots });
|
|
1043
1046
|
}
|
|
1044
1047
|
this.scroller.append(this.grid);
|
|
1045
1048
|
this.el.append(this.scroller);
|
|
1046
1049
|
this.el.classList.toggle("tnc-dg--auto", opts.height === "auto");
|
|
1047
1050
|
const RO = ctx.win.ResizeObserver;
|
|
1048
|
-
if (RO
|
|
1051
|
+
if (RO) {
|
|
1049
1052
|
this.resizeObs = new RO(() => {
|
|
1050
1053
|
if (this.sizeKey() !== this.lastHeights) this.render(null);
|
|
1051
1054
|
});
|
|
@@ -1076,14 +1079,35 @@ var DayGridView = class {
|
|
|
1076
1079
|
}
|
|
1077
1080
|
render(_change) {
|
|
1078
1081
|
const { ctx } = this;
|
|
1082
|
+
this.dotsMode = ctx.opts.monthDisplay !== "full" || this.grid.clientWidth / this.daysPerWeek() < 72;
|
|
1083
|
+
this.el.classList.toggle("tnc-dg--dots", this.dotsMode);
|
|
1084
|
+
this.el.classList.toggle("tnc-dg--compact", this.dotsMode && ctx.opts.monthDisplay === "full");
|
|
1085
|
+
this.el.classList.toggle("tnc-dg--chips", ctx.opts.monthDisplay === "chips" && this.weeks[0].dots[0].box.clientHeight >= 44);
|
|
1079
1086
|
const cs = ctx.win.getComputedStyle(this.el);
|
|
1080
1087
|
this.barPx = px(cs, "--tnc-bar-h", 20);
|
|
1081
1088
|
this.topPx = this.weeks[0]?.cells[0]?.querySelector(".tnc-dg-top")?.offsetHeight || px(cs, "--tnc-dg-top-h", 24);
|
|
1082
1089
|
this.morePx = this.measureMore(cs);
|
|
1083
1090
|
this.lastHeights = this.sizeKey();
|
|
1084
1091
|
for (const w of this.weeks) {
|
|
1085
|
-
|
|
1086
|
-
|
|
1092
|
+
for (const cell of w.cells) {
|
|
1093
|
+
if (this.dotsMode) cell.tabIndex = 0;
|
|
1094
|
+
else cell.removeAttribute("tabindex");
|
|
1095
|
+
if (this.dotsMode && ctx.opts.monthDisplay === "full") cell.setAttribute("aria-haspopup", "dialog");
|
|
1096
|
+
else cell.removeAttribute("aria-haspopup");
|
|
1097
|
+
}
|
|
1098
|
+
for (const cell of w.dots) cell.box.hidden = !this.dotsMode;
|
|
1099
|
+
if (this.dotsMode) {
|
|
1100
|
+
w.pool.begin();
|
|
1101
|
+
w.pool.end();
|
|
1102
|
+
w.more.replaceChildren();
|
|
1103
|
+
this.renderDots(w);
|
|
1104
|
+
} else {
|
|
1105
|
+
for (const cell of w.dots) {
|
|
1106
|
+
cell.pool.begin();
|
|
1107
|
+
cell.pool.end();
|
|
1108
|
+
}
|
|
1109
|
+
this.renderWeek(w);
|
|
1110
|
+
}
|
|
1087
1111
|
}
|
|
1088
1112
|
}
|
|
1089
1113
|
/** Height of a "+N more" link as the host styles it (`--tnc-more-h`, or a taller min-height). */
|
|
@@ -1169,19 +1193,28 @@ var DayGridView = class {
|
|
|
1169
1193
|
});
|
|
1170
1194
|
}
|
|
1171
1195
|
w.pool.end();
|
|
1172
|
-
w.more.
|
|
1196
|
+
const existing = new Map(Array.from(w.more.children, (element) => [element.getAttribute("data-day"), element]));
|
|
1173
1197
|
hidden.forEach((items, col) => {
|
|
1174
1198
|
if (!items.length) return;
|
|
1175
1199
|
const day = w.days[col];
|
|
1176
|
-
const
|
|
1200
|
+
const key = String(day);
|
|
1201
|
+
let link = existing.get(key);
|
|
1202
|
+
if (!link) {
|
|
1203
|
+
link = h(ctx.doc, "button", "tnc-more", { type: "button", "aria-haspopup": "dialog", "data-day": key });
|
|
1204
|
+
const anchor = link;
|
|
1205
|
+
link.addEventListener("click", (ev) => ctx.openMore(day, anchor, ev));
|
|
1206
|
+
w.more.append(link);
|
|
1207
|
+
}
|
|
1208
|
+
existing.delete(key);
|
|
1209
|
+
link.setAttribute("aria-label", ctx.text.more(items.length));
|
|
1210
|
+
link.textContent = w.row.clientWidth / n < 72 ? `+${new Intl.NumberFormat(ctx.opts.locale).format(items.length)}` : ctx.text.more(items.length);
|
|
1177
1211
|
setStyle(link, {
|
|
1178
1212
|
left: `calc(${pct(col / n)} + 2px)`,
|
|
1179
1213
|
width: `calc(${pct(1 / n)} - 4px)`,
|
|
1180
1214
|
top: `calc(var(--tnc-dg-top-h) + ${visible(col)} * (var(--tnc-bar-h) + ${BAR_GAP}px))`
|
|
1181
1215
|
});
|
|
1182
|
-
link.addEventListener("click", (ev) => ctx.openMore(day, link, ev));
|
|
1183
|
-
w.more.append(link);
|
|
1184
1216
|
});
|
|
1217
|
+
for (const link of existing.values()) link.remove();
|
|
1185
1218
|
const step = this.barPx + BAR_GAP;
|
|
1186
1219
|
let need = maxLevel >= 0 ? (maxLevel + 1) * step - BAR_GAP : 0;
|
|
1187
1220
|
hidden.forEach((items, col) => {
|
|
@@ -1204,10 +1237,11 @@ var DayGridView = class {
|
|
|
1204
1237
|
const { dayMaxEvents } = ctx.opts;
|
|
1205
1238
|
const first = w.dots[0].box;
|
|
1206
1239
|
const cs = ctx.win.getComputedStyle(first);
|
|
1207
|
-
const
|
|
1240
|
+
const chips = this.el.classList.contains("tnc-dg--chips");
|
|
1241
|
+
const size = chips ? px(cs, "--tnc-chip-h", 14) : px(cs, "--tnc-dot-size", 8);
|
|
1208
1242
|
const gap = px(cs, "--tnc-dot-gap", 3);
|
|
1209
1243
|
const step = size + gap;
|
|
1210
|
-
const perRow = Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
1244
|
+
const perRow = chips ? 1 : Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
1211
1245
|
const rows = Math.max(1, Math.floor((first.clientHeight + gap) / step));
|
|
1212
1246
|
const fits = dayMaxEvents === false ? Infinity : perRow * rows;
|
|
1213
1247
|
const cap = typeof dayMaxEvents === "number" && dayMaxEvents > 0 ? Math.min(dayMaxEvents, fits) : fits;
|
|
@@ -1216,7 +1250,7 @@ var DayGridView = class {
|
|
|
1216
1250
|
w.days.forEach((day, i) => {
|
|
1217
1251
|
const cell = w.dots[i];
|
|
1218
1252
|
const items = this.itemsOn(day);
|
|
1219
|
-
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(
|
|
1253
|
+
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(1, cap - (chips ? 1 : 2)) : Math.max(1, cap);
|
|
1220
1254
|
cell.pool.begin();
|
|
1221
1255
|
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 }));
|
|
1222
1256
|
cell.pool.end();
|
|
@@ -1333,6 +1367,7 @@ var ListView = class {
|
|
|
1333
1367
|
const leadItems = [];
|
|
1334
1368
|
const inLead = /* @__PURE__ */ new Set();
|
|
1335
1369
|
const keep = /* @__PURE__ */ new Set();
|
|
1370
|
+
let itemCount = 0;
|
|
1336
1371
|
let prev = null;
|
|
1337
1372
|
for (const day of days) {
|
|
1338
1373
|
let items = this.itemsOn(day);
|
|
@@ -1346,7 +1381,7 @@ var ListView = class {
|
|
|
1346
1381
|
return false;
|
|
1347
1382
|
});
|
|
1348
1383
|
}
|
|
1349
|
-
if (!items.length && !agenda) continue;
|
|
1384
|
+
if (!items.length && !agenda && opts.listDayCount !== 1) continue;
|
|
1350
1385
|
keep.add(day);
|
|
1351
1386
|
let section = this.sections.get(day);
|
|
1352
1387
|
let pool = this.pools.get(day);
|
|
@@ -1365,6 +1400,7 @@ var ListView = class {
|
|
|
1365
1400
|
this.pools.set(day, pool);
|
|
1366
1401
|
}
|
|
1367
1402
|
section.classList.toggle("is-empty", !items.length);
|
|
1403
|
+
itemCount += items.length;
|
|
1368
1404
|
this.fill(pool, items, () => day);
|
|
1369
1405
|
if (prev ? prev.nextSibling !== section : this.body.firstChild !== section) {
|
|
1370
1406
|
if (prev) prev.after(section);
|
|
@@ -1380,11 +1416,14 @@ var ListView = class {
|
|
|
1380
1416
|
this.sections.delete(day);
|
|
1381
1417
|
}
|
|
1382
1418
|
this.renderLead(leadItems);
|
|
1383
|
-
let empty = this.body.querySelector("
|
|
1384
|
-
if (!
|
|
1419
|
+
let empty = this.body.querySelector(".tnc-list-empty");
|
|
1420
|
+
if (!itemCount && !leadItems.length && (!agenda || opts.listEmptyContent)) {
|
|
1385
1421
|
if (!empty) {
|
|
1386
1422
|
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
1387
|
-
|
|
1423
|
+
const content = ctx.opts.listEmptyContent?.();
|
|
1424
|
+
if (content) empty.replaceChildren(content);
|
|
1425
|
+
if (this.sections.size === 1) this.sections.values().next().value.append(empty);
|
|
1426
|
+
else this.body.prepend(empty);
|
|
1388
1427
|
}
|
|
1389
1428
|
} else empty?.remove();
|
|
1390
1429
|
this.ctx.strip?.draw();
|
|
@@ -1442,7 +1481,7 @@ var ListView = class {
|
|
|
1442
1481
|
const section = day === this.ctx.days[0] && this.lead ? this.lead.section : this.sections.get(day);
|
|
1443
1482
|
if (!section) return;
|
|
1444
1483
|
this.ctx.strip?.measure();
|
|
1445
|
-
section.
|
|
1484
|
+
this.scroller.scrollTop += section.getBoundingClientRect().top - this.scroller.getBoundingClientRect().top;
|
|
1446
1485
|
this.ctx.strip?.setFocus(day);
|
|
1447
1486
|
this.pinned = true;
|
|
1448
1487
|
}
|
|
@@ -1531,8 +1570,7 @@ var DateStrip = class {
|
|
|
1531
1570
|
head.append(this.title, this.todayBtn, prev, next, this.slot);
|
|
1532
1571
|
this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
|
|
1533
1572
|
this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
|
|
1534
|
-
const handle = h(doc, "
|
|
1535
|
-
handle.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
1573
|
+
const handle = h(doc, "div", "tnc-strip-handle", { "aria-hidden": "true" });
|
|
1536
1574
|
this.el.append(head, this.dows, this.grid, handle);
|
|
1537
1575
|
this.grid.addEventListener("click", this.onClick);
|
|
1538
1576
|
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
@@ -1732,6 +1770,8 @@ var TimeGridView = class {
|
|
|
1732
1770
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1733
1771
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1734
1772
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1773
|
+
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1774
|
+
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
1735
1775
|
this.top = h(doc, "div", "tnc-tg-top");
|
|
1736
1776
|
this.headRow = h(doc, "div", "tnc-tg-head tnc-tg-cols", { role: "row" });
|
|
1737
1777
|
this.top.append(this.headRow);
|
|
@@ -1759,7 +1799,7 @@ var TimeGridView = class {
|
|
|
1759
1799
|
} else {
|
|
1760
1800
|
this.body = this.gutter = null;
|
|
1761
1801
|
}
|
|
1762
|
-
this.el.append(this.scroller);
|
|
1802
|
+
this.el.append(this.dayNavigation, this.scroller);
|
|
1763
1803
|
this.build();
|
|
1764
1804
|
if (showGrid && opts.height === "fill") {
|
|
1765
1805
|
const RO = ctx.win.ResizeObserver;
|
|
@@ -1772,6 +1812,7 @@ var TimeGridView = class {
|
|
|
1772
1812
|
ctx;
|
|
1773
1813
|
el;
|
|
1774
1814
|
scroller;
|
|
1815
|
+
dayNavigation;
|
|
1775
1816
|
top;
|
|
1776
1817
|
headRow;
|
|
1777
1818
|
allDayRow;
|
|
@@ -1827,6 +1868,9 @@ var TimeGridView = class {
|
|
|
1827
1868
|
head.append(cap);
|
|
1828
1869
|
}
|
|
1829
1870
|
this.headRow.append(head);
|
|
1871
|
+
const dayButton = h(doc, "button", "tnc-btn", { type: "button", "aria-label": ctx.fmt.longDate(day) }, `${ctx.fmt.weekdayShort(day)} ${ctx.fmt.dayNumber(day)}`);
|
|
1872
|
+
dayButton.addEventListener("click", () => this.revealDay(day));
|
|
1873
|
+
this.dayNavigation.append(dayButton);
|
|
1830
1874
|
opts.dayHeaderDidMount?.({ ...arg, el: head });
|
|
1831
1875
|
const col = h(doc, "div", ["tnc-col", ...ctx.dayClasses(day)].join(" "), { role: "cell", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr });
|
|
1832
1876
|
const bgLayer = h(doc, "div", "tnc-col-bg");
|
|
@@ -1843,6 +1887,7 @@ var TimeGridView = class {
|
|
|
1843
1887
|
this.body?.append(col);
|
|
1844
1888
|
return {
|
|
1845
1889
|
day,
|
|
1890
|
+
collisionColumns: 1,
|
|
1846
1891
|
head,
|
|
1847
1892
|
col,
|
|
1848
1893
|
plan: new ItemPool(ctx, planLayer),
|
|
@@ -1899,12 +1944,17 @@ var TimeGridView = class {
|
|
|
1899
1944
|
/* --------------------------------------------------------------- render */
|
|
1900
1945
|
render(change) {
|
|
1901
1946
|
this.renderAllDay();
|
|
1902
|
-
if (!this.body)
|
|
1947
|
+
if (!this.body) {
|
|
1948
|
+
this.updateDayNavigation();
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1903
1951
|
const dirty = change ? change.days : null;
|
|
1904
1952
|
for (const c of this.cols) {
|
|
1905
1953
|
if (dirty && !dirty.has(c.day) && !(this.ctx.slot.max > MINUTES_PER_DAY && dirty.has(c.day + 1))) continue;
|
|
1906
1954
|
this.renderColumn(c);
|
|
1907
1955
|
}
|
|
1956
|
+
this.el.style.setProperty("--tnc-collision-columns", String(Math.max(1, ...this.cols.map((column) => column.collisionColumns))));
|
|
1957
|
+
this.updateDayNavigation();
|
|
1908
1958
|
this.renderCapacity();
|
|
1909
1959
|
this.tick();
|
|
1910
1960
|
}
|
|
@@ -2022,6 +2072,11 @@ var TimeGridView = class {
|
|
|
2022
2072
|
}
|
|
2023
2073
|
const minMinutes = opts.eventMinHeight / this.ppm;
|
|
2024
2074
|
const layout = layoutTimedDay(items, c.day, zone, minMinutes, ctx.order, [slot.min, slot.max]);
|
|
2075
|
+
c.collisionColumns = Math.max(
|
|
2076
|
+
1,
|
|
2077
|
+
...layout.plan.map((placement) => opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols),
|
|
2078
|
+
...layout.actual.filter((placement) => !placement.split).map((placement) => placement.cols)
|
|
2079
|
+
);
|
|
2025
2080
|
const total = this.minutes;
|
|
2026
2081
|
const top = (m) => pct((m - slot.min) / total);
|
|
2027
2082
|
const height = (s) => pct((s.endMin - s.startMin) / total);
|
|
@@ -2033,14 +2088,22 @@ var TimeGridView = class {
|
|
|
2033
2088
|
c.bg.end();
|
|
2034
2089
|
const hasActual = layout.actual.length > 0;
|
|
2035
2090
|
c.col.classList.toggle("has-actual", hasActual);
|
|
2091
|
+
for (const link of c.more.children) link.setAttribute("data-stale", "true");
|
|
2036
2092
|
this.placeLane(c.plan, layout.plan, c, top, height, "plan");
|
|
2037
2093
|
this.placeLane(c.actual, layout.actual, c, top, height, "actual");
|
|
2094
|
+
for (const link of [...c.more.children]) if (link.hasAttribute("data-stale")) link.remove();
|
|
2038
2095
|
}
|
|
2039
2096
|
placeLane(pool, placements, c, top, height, lane) {
|
|
2040
2097
|
const { ctx } = this;
|
|
2041
2098
|
const { opts } = ctx;
|
|
2042
|
-
const
|
|
2043
|
-
const
|
|
2099
|
+
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
2100
|
+
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
2101
|
+
const collapse = placements.some((placement) => {
|
|
2102
|
+
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
2103
|
+
return columns > 1 && columns * readableWidth > laneWidth;
|
|
2104
|
+
});
|
|
2105
|
+
const withMore = lane === "plan" || collapse;
|
|
2106
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2044
2107
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
2045
2108
|
pool.begin();
|
|
2046
2109
|
for (const p of placements) {
|
|
@@ -2066,21 +2129,46 @@ var TimeGridView = class {
|
|
|
2066
2129
|
top: top(p.startMin),
|
|
2067
2130
|
height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
2068
2131
|
...inLane(lane, p.split, left, width),
|
|
2132
|
+
"--tnc-event-readable-width": pct(span / cols / width),
|
|
2069
2133
|
"z-index": String(p.col + 1)
|
|
2070
2134
|
});
|
|
2071
2135
|
}
|
|
2072
2136
|
pool.end();
|
|
2073
2137
|
if (!withMore) return;
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2138
|
+
for (const [cluster, rec] of hiddenByCluster) {
|
|
2139
|
+
const key = `${lane}-${cluster}`;
|
|
2140
|
+
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
2141
|
+
if (!link) {
|
|
2142
|
+
link = h(ctx.doc, "button", "tnc-more tnc-more--stack", { type: "button", "aria-haspopup": "dialog", "data-cluster": key });
|
|
2143
|
+
const anchor = link;
|
|
2144
|
+
link.addEventListener("click", (ev) => ctx.openMore(c.day, anchor, ev, JSON.parse(anchor.dataset.itemIds ?? "[]")));
|
|
2145
|
+
c.more.append(link);
|
|
2146
|
+
}
|
|
2147
|
+
link.removeAttribute("data-stale");
|
|
2148
|
+
link.dataset.itemIds = JSON.stringify(placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.id));
|
|
2149
|
+
link.classList.toggle("tnc-more--collision", collapse);
|
|
2150
|
+
link.textContent = `+${rec.count}`;
|
|
2151
|
+
if (collapse) {
|
|
2152
|
+
const items = placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.item.title);
|
|
2153
|
+
const count = ctx.text.appointments(rec.count);
|
|
2154
|
+
const interval = `${ctx.fmt.time(rec.start)} \u2013 ${ctx.fmt.time(rec.end)}`;
|
|
2155
|
+
link.replaceChildren(
|
|
2156
|
+
h(ctx.doc, "span", "tnc-collision-count", null, String(rec.count)),
|
|
2157
|
+
h(ctx.doc, "span", "tnc-collision-range", null, interval),
|
|
2158
|
+
h(ctx.doc, "span", "tnc-collision-title", { "aria-hidden": "true" }, items.join(" \xB7 "))
|
|
2159
|
+
);
|
|
2160
|
+
link.setAttribute("aria-label", `${count}, ${interval}: ${items.join(", ")}`);
|
|
2161
|
+
link.title = link.getAttribute("aria-label");
|
|
2162
|
+
} else {
|
|
2163
|
+
link.removeAttribute("aria-label");
|
|
2164
|
+
link.removeAttribute("title");
|
|
2165
|
+
}
|
|
2077
2166
|
const cols = maxStack;
|
|
2078
2167
|
setStyle(link, {
|
|
2079
2168
|
top: top(rec.start),
|
|
2169
|
+
height: collapse ? pct((rec.end - rec.start) / this.minutes) : "",
|
|
2080
2170
|
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
2081
2171
|
});
|
|
2082
|
-
link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
|
|
2083
|
-
c.more.append(link);
|
|
2084
2172
|
}
|
|
2085
2173
|
}
|
|
2086
2174
|
meta(s, bg) {
|
|
@@ -2145,12 +2233,10 @@ var TimeGridView = class {
|
|
|
2145
2233
|
const r2 = cells[i2].getBoundingClientRect();
|
|
2146
2234
|
if (x >= r2.left && x < r2.right) return i2;
|
|
2147
2235
|
}
|
|
2148
|
-
const firstR = cells[0]?.getBoundingClientRect();
|
|
2149
|
-
const lastR = cells[cells.length - 1]?.getBoundingClientRect();
|
|
2150
|
-
if (firstR && x < firstR.left) return 0;
|
|
2151
|
-
if (lastR && x >= lastR.right) return cells.length - 1;
|
|
2152
2236
|
return -1;
|
|
2153
2237
|
};
|
|
2238
|
+
const bounds = this.scroller.getBoundingClientRect();
|
|
2239
|
+
if (x < bounds.left || x >= bounds.right || y < bounds.top || y >= bounds.bottom) return null;
|
|
2154
2240
|
if (this.allDayRow) {
|
|
2155
2241
|
const r2 = this.allDayRow.getBoundingClientRect();
|
|
2156
2242
|
if (y >= r2.top && y < r2.bottom || !this.body && y >= r2.top) {
|
|
@@ -2191,6 +2277,26 @@ var TimeGridView = class {
|
|
|
2191
2277
|
}
|
|
2192
2278
|
resize() {
|
|
2193
2279
|
this.fitRows();
|
|
2280
|
+
this.render(null);
|
|
2281
|
+
}
|
|
2282
|
+
revealDay(day) {
|
|
2283
|
+
const column = this.cols.find((candidate) => candidate.day === day);
|
|
2284
|
+
if (!column) return;
|
|
2285
|
+
const bounds = column.head.getBoundingClientRect();
|
|
2286
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2287
|
+
this.scroller.scrollLeft += bounds.left - viewport.left - (viewport.width - bounds.width) / 2;
|
|
2288
|
+
this.updateDayNavigation();
|
|
2289
|
+
}
|
|
2290
|
+
updateDayNavigation() {
|
|
2291
|
+
this.dayNavigation.hidden = this.scroller.scrollWidth <= this.scroller.clientWidth + 1;
|
|
2292
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2293
|
+
this.cols.forEach((column, index) => {
|
|
2294
|
+
const bounds = column.head.getBoundingClientRect();
|
|
2295
|
+
const visible = bounds.left >= viewport.left && bounds.right <= viewport.right;
|
|
2296
|
+
const button = this.dayNavigation.children[index];
|
|
2297
|
+
button?.classList.toggle("is-offscreen", !visible);
|
|
2298
|
+
button?.setAttribute("aria-pressed", String(visible));
|
|
2299
|
+
});
|
|
2194
2300
|
}
|
|
2195
2301
|
scrollToTime(minute) {
|
|
2196
2302
|
if (!this.body) return;
|
|
@@ -2853,6 +2959,14 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2853
2959
|
setScroll(s) {
|
|
2854
2960
|
this.scroller.scrollTop = s.top;
|
|
2855
2961
|
}
|
|
2962
|
+
revealDay(day) {
|
|
2963
|
+
const month = this.cells.find((cell) => cell.day === day)?.el.closest(".tnc-ym");
|
|
2964
|
+
if (!month) return;
|
|
2965
|
+
const bounds = month.getBoundingClientRect();
|
|
2966
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2967
|
+
if (bounds.top < viewport.top) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
2968
|
+
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.bottom - viewport.bottom;
|
|
2969
|
+
}
|
|
2856
2970
|
elementsFor(_id) {
|
|
2857
2971
|
return [];
|
|
2858
2972
|
}
|
|
@@ -2932,6 +3046,11 @@ var Calendar = class {
|
|
|
2932
3046
|
conflicts = /* @__PURE__ */ new Map();
|
|
2933
3047
|
slot = { min: 0, max: MINUTES_PER_DAY, duration: 30, snap: 30, scroll: 480 };
|
|
2934
3048
|
draggingId = null;
|
|
3049
|
+
listDragPreview = null;
|
|
3050
|
+
dragSources = [];
|
|
3051
|
+
dragScrollFrame = null;
|
|
3052
|
+
dragScrollOrigins = /* @__PURE__ */ new Map();
|
|
3053
|
+
itemWriteStates = /* @__PURE__ */ new Map();
|
|
2935
3054
|
dragRow = null;
|
|
2936
3055
|
strip = null;
|
|
2937
3056
|
stripSize = null;
|
|
@@ -2945,6 +3064,7 @@ var Calendar = class {
|
|
|
2945
3064
|
session = null;
|
|
2946
3065
|
selection = null;
|
|
2947
3066
|
suppressClick = false;
|
|
3067
|
+
cancelledPointerId = null;
|
|
2948
3068
|
selectedIds = /* @__PURE__ */ new Set();
|
|
2949
3069
|
popover = null;
|
|
2950
3070
|
timer = null;
|
|
@@ -2956,8 +3076,13 @@ var Calendar = class {
|
|
|
2956
3076
|
this.resizeObs?.disconnect();
|
|
2957
3077
|
const RO = this.win.ResizeObserver;
|
|
2958
3078
|
if (RO) {
|
|
2959
|
-
this.resizeObs = new RO(() =>
|
|
3079
|
+
this.resizeObs = new RO(() => {
|
|
3080
|
+
this.syncToolbarViewDisplay();
|
|
3081
|
+
this.current?.resize?.();
|
|
3082
|
+
this.positionMore();
|
|
3083
|
+
});
|
|
2960
3084
|
this.resizeObs.observe(this.root);
|
|
3085
|
+
this.resizeObs.observe(this.toolbarEl);
|
|
2961
3086
|
}
|
|
2962
3087
|
}
|
|
2963
3088
|
onAdoptCheck = () => {
|
|
@@ -2976,9 +3101,11 @@ var Calendar = class {
|
|
|
2976
3101
|
if (this.frame !== null) old.cancelAnimationFrame(this.frame);
|
|
2977
3102
|
this.timer = this.frame = null;
|
|
2978
3103
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3104
|
+
this.listenMoreGeometry(false);
|
|
2979
3105
|
this.doc = doc;
|
|
2980
3106
|
this.win = doc.defaultView ?? old;
|
|
2981
3107
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3108
|
+
this.listenMoreGeometry(true);
|
|
2982
3109
|
this.observeResize();
|
|
2983
3110
|
this.rebuild(false);
|
|
2984
3111
|
this.scheduleTick();
|
|
@@ -3082,7 +3209,12 @@ var Calendar = class {
|
|
|
3082
3209
|
gotoDate(date) {
|
|
3083
3210
|
const day = this.toDay(date);
|
|
3084
3211
|
if (day === null) return;
|
|
3212
|
+
const sameMonth = this.opts.view === "month" && startOfMonth(day) === startOfMonth(this.anchor);
|
|
3085
3213
|
this.anchor = day;
|
|
3214
|
+
if (sameMonth) {
|
|
3215
|
+
this.view = { ...this.view, anchor: day };
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3086
3218
|
this.rebuild(true);
|
|
3087
3219
|
}
|
|
3088
3220
|
prev() {
|
|
@@ -3096,6 +3228,7 @@ var Calendar = class {
|
|
|
3096
3228
|
goToday() {
|
|
3097
3229
|
this.anchor = this.todayDay();
|
|
3098
3230
|
this.rebuild(true);
|
|
3231
|
+
if (this.current instanceof TimeGridView) this.current.revealDay(this.anchor);
|
|
3099
3232
|
}
|
|
3100
3233
|
/** The anchor date, as local midnight of that civil day in the calendar zone. */
|
|
3101
3234
|
getDate() {
|
|
@@ -3114,6 +3247,7 @@ var Calendar = class {
|
|
|
3114
3247
|
/** Re-measures (e.g. after the host was shown or resized by a parent). */
|
|
3115
3248
|
updateSize() {
|
|
3116
3249
|
this.current?.resize?.();
|
|
3250
|
+
this.positionMore();
|
|
3117
3251
|
}
|
|
3118
3252
|
getScroll() {
|
|
3119
3253
|
return this.current?.getScroll() ?? { top: 0, left: 0 };
|
|
@@ -3145,7 +3279,10 @@ var Calendar = class {
|
|
|
3145
3279
|
}
|
|
3146
3280
|
const view = this.current;
|
|
3147
3281
|
if (view instanceof TimelineView) view.scrollToInstant(ms, opts.align ?? "center", opts.smooth ?? false);
|
|
3148
|
-
else if (view instanceof TimeGridView
|
|
3282
|
+
else if (view instanceof TimeGridView) {
|
|
3283
|
+
view.revealDay(day);
|
|
3284
|
+
if (p.kind !== "day") view.scrollToTime(wallTime(ms, this.zone).minute);
|
|
3285
|
+
}
|
|
3149
3286
|
}
|
|
3150
3287
|
/** What is under a client point: a time slot, a day, or a timeline row and date. Null outside the view. */
|
|
3151
3288
|
hitTest(clientX, clientY) {
|
|
@@ -3223,7 +3360,8 @@ var Calendar = class {
|
|
|
3223
3360
|
out.push(...classList(this.opts.dayClassNames?.(this.dayArg(day))));
|
|
3224
3361
|
return out;
|
|
3225
3362
|
}
|
|
3226
|
-
isEditable(n, what) {
|
|
3363
|
+
isEditable(n, what, presentation = false) {
|
|
3364
|
+
if (!presentation && this.itemWriteStates.get(n.id) === "pending") return false;
|
|
3227
3365
|
if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
|
|
3228
3366
|
if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
|
|
3229
3367
|
const it = n.item;
|
|
@@ -3263,13 +3401,14 @@ var Calendar = class {
|
|
|
3263
3401
|
if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
|
|
3264
3402
|
if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
|
|
3265
3403
|
const indicator = variant === "indicator";
|
|
3266
|
-
if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
|
|
3404
|
+
if (!preview && !indicator && (this.isEditable(n, "start", true) || this.isEditable(n, "duration", true))) el.classList.add("is-draggable");
|
|
3267
3405
|
if (this.draggingId === n.id) el.classList.add("is-dragging");
|
|
3268
3406
|
if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
|
|
3269
3407
|
el.classList.add(...classList(it.className));
|
|
3270
3408
|
const arg = this.itemArg(el, meta);
|
|
3271
3409
|
el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
|
|
3272
3410
|
el.dataset.id = n.id;
|
|
3411
|
+
this.applyItemWriteState(el, n.id);
|
|
3273
3412
|
if (it.kind) el.dataset.kind = it.kind;
|
|
3274
3413
|
const color = it.color ?? it.borderColor;
|
|
3275
3414
|
if (color) el.style.setProperty("--tnc-c", color);
|
|
@@ -3286,6 +3425,7 @@ var Calendar = class {
|
|
|
3286
3425
|
const custom = variant === "list" ? this.opts.listItemContent?.(arg) : null;
|
|
3287
3426
|
const content = indicator ? null : custom ?? this.opts.itemContent?.(arg);
|
|
3288
3427
|
if (indicator) {
|
|
3428
|
+
if (this.opts.monthDisplay === "chips") el.append(h(this.doc, "span", "tnc-item-title", null, it.title));
|
|
3289
3429
|
} else if (content != null) {
|
|
3290
3430
|
const wrap = h(this.doc, "div", "tnc-item-main");
|
|
3291
3431
|
wrap.append(content);
|
|
@@ -3298,6 +3438,7 @@ var Calendar = class {
|
|
|
3298
3438
|
return el;
|
|
3299
3439
|
}
|
|
3300
3440
|
refreshItemEl(el, meta) {
|
|
3441
|
+
this.applyItemWriteState(el, meta.n.id);
|
|
3301
3442
|
const time = el.querySelector(":scope .tnc-item-time");
|
|
3302
3443
|
if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
|
|
3303
3444
|
el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
|
|
@@ -3320,9 +3461,10 @@ var Calendar = class {
|
|
|
3320
3461
|
if (meta) this.opts.itemWillUnmount?.(this.itemArg(el, meta));
|
|
3321
3462
|
el.remove();
|
|
3322
3463
|
}
|
|
3323
|
-
openMore(day, anchor, jsEvent) {
|
|
3464
|
+
openMore(day, anchor, jsEvent, itemIds) {
|
|
3324
3465
|
jsEvent.stopPropagation();
|
|
3325
|
-
const
|
|
3466
|
+
const group = itemIds ? new Set(itemIds) : null;
|
|
3467
|
+
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));
|
|
3326
3468
|
const shown = new Set(
|
|
3327
3469
|
[...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 ?? "")
|
|
3328
3470
|
);
|
|
@@ -3351,15 +3493,7 @@ var Calendar = class {
|
|
|
3351
3493
|
}
|
|
3352
3494
|
pool.end();
|
|
3353
3495
|
this.popover = { el, pool, anchor, day };
|
|
3354
|
-
|
|
3355
|
-
const ar = anchor.getBoundingClientRect();
|
|
3356
|
-
const w = Math.max(220, ar.width + 24);
|
|
3357
|
-
let left = ar.left - rr.left - 12;
|
|
3358
|
-
left = Math.max(4, Math.min(left, rr.width - w - 4));
|
|
3359
|
-
let top = ar.top - rr.top - 40;
|
|
3360
|
-
const height = el.offsetHeight;
|
|
3361
|
-
top = Math.max(4, Math.min(top, rr.height - height - 4));
|
|
3362
|
-
setStyle(el, { left, top, width: w });
|
|
3496
|
+
this.positionMore();
|
|
3363
3497
|
el.focus();
|
|
3364
3498
|
}
|
|
3365
3499
|
/* =============================================================== render */
|
|
@@ -3483,8 +3617,11 @@ var Calendar = class {
|
|
|
3483
3617
|
this.current.setScroll({ top: keep.top, left: 0 });
|
|
3484
3618
|
this.current.placeInstant(keep.ms, keep.vx);
|
|
3485
3619
|
} else if (scroll) this.current.setScroll(scroll);
|
|
3486
|
-
else if (this.current instanceof TimeGridView)
|
|
3487
|
-
|
|
3620
|
+
else if (this.current instanceof TimeGridView) {
|
|
3621
|
+
this.current.scrollToTime(this.slot.scroll);
|
|
3622
|
+
this.current.revealDay(this.anchor);
|
|
3623
|
+
} else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
3624
|
+
else if (this.current instanceof YearView) this.current.revealDay(this.anchor);
|
|
3488
3625
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
3489
3626
|
}
|
|
3490
3627
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -3510,9 +3647,11 @@ var Calendar = class {
|
|
|
3510
3647
|
}
|
|
3511
3648
|
/** The range, run on to the end of the strip's days so their dots are right. */
|
|
3512
3649
|
dataRangeFor(range) {
|
|
3513
|
-
const
|
|
3514
|
-
|
|
3515
|
-
|
|
3650
|
+
const window = this.strip?.window();
|
|
3651
|
+
const start = window ? Math.min(range.startDay, window.start) : range.startDay;
|
|
3652
|
+
const end = window ? Math.max(range.endDay, window.end) : range.endDay;
|
|
3653
|
+
if (start === range.startDay && end === range.endDay) return range;
|
|
3654
|
+
return { ...range, startDay: start, start: new Date(toInstant(start, 0, this.zone)), endDay: end, end: new Date(toInstant(end, 0, this.zone)) };
|
|
3516
3655
|
}
|
|
3517
3656
|
/** Adds, updates or removes the date strip for the current view. */
|
|
3518
3657
|
syncStrip(navigated) {
|
|
@@ -3559,20 +3698,24 @@ var Calendar = class {
|
|
|
3559
3698
|
stripShown() {
|
|
3560
3699
|
if (!this.strip || this.rebuilding || !this.view) return;
|
|
3561
3700
|
const next = this.dataRangeFor(this.view.range);
|
|
3562
|
-
if (next.endDay === this.view.dataRange.endDay) return;
|
|
3701
|
+
if (next.startDay === this.view.dataRange.startDay && next.endDay === this.view.dataRange.endDay) return;
|
|
3563
3702
|
this.view = { ...this.view, dataRange: next };
|
|
3564
3703
|
this.opts.onRangeChange?.(this.view);
|
|
3565
3704
|
}
|
|
3566
3705
|
/** What is under a point, the date strip included. */
|
|
3567
3706
|
hitAt(x, y) {
|
|
3707
|
+
const target = this.doc.elementFromPoint(x, y);
|
|
3708
|
+
if (!target || !this.root.contains(target)) {
|
|
3709
|
+
this.strip?.markTarget(null);
|
|
3710
|
+
return null;
|
|
3711
|
+
}
|
|
3568
3712
|
const strip = this.strip;
|
|
3569
3713
|
if (strip) {
|
|
3570
|
-
const
|
|
3571
|
-
const day = el ? strip.dayAt(el) : null;
|
|
3714
|
+
const day = strip.dayAt(target);
|
|
3572
3715
|
strip.markTarget(day !== null && this.draggingId ? day : null);
|
|
3573
3716
|
if (day !== null) return { kind: "day", day };
|
|
3574
3717
|
}
|
|
3575
|
-
return this.current?.hit(x, y)
|
|
3718
|
+
return this.current?.el.contains(target) ? this.current.hit(x, y) : null;
|
|
3576
3719
|
}
|
|
3577
3720
|
agendaList() {
|
|
3578
3721
|
return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
|
|
@@ -3655,17 +3798,44 @@ var Calendar = class {
|
|
|
3655
3798
|
);
|
|
3656
3799
|
const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
|
|
3657
3800
|
const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
|
|
3801
|
+
const viewSelect = h(this.doc, "select", "tnc-btn tnc-toolbar-view-select", {
|
|
3802
|
+
"aria-label": this.text.viewHints[this.opts.view] ?? this.text.views[this.opts.view] ?? this.opts.view
|
|
3803
|
+
});
|
|
3658
3804
|
for (const v of views) {
|
|
3659
3805
|
const label = v === "days" ? this.text.views.days ?? `${this.opts.dayCount} days` : this.text.views[v] ?? v;
|
|
3660
3806
|
viewGroup.append(btn(label, this.text.viewHints[v] ?? label, `tnc-btn-view tnc-btn-view-${v}`, () => this.changeView(v), v === this.opts.view));
|
|
3807
|
+
viewSelect.append(h(this.doc, "option", "", { value: v }, label));
|
|
3661
3808
|
}
|
|
3809
|
+
viewSelect.value = this.opts.view;
|
|
3810
|
+
viewSelect.addEventListener("change", () => this.changeView(viewSelect.value));
|
|
3662
3811
|
const start = h(this.doc, "div", "tnc-toolbar-start");
|
|
3663
3812
|
const end = h(this.doc, "div", "tnc-toolbar-end");
|
|
3664
3813
|
if (cfg.extraPlacement === "start") start.append(...extras);
|
|
3665
3814
|
start.append(nav, title);
|
|
3666
|
-
end.append(viewGroup);
|
|
3815
|
+
end.append(viewGroup, viewSelect);
|
|
3667
3816
|
if (cfg.extraPlacement !== "start") end.append(...extras);
|
|
3668
3817
|
this.toolbarEl.append(start, end);
|
|
3818
|
+
this.syncToolbarViewDisplay();
|
|
3819
|
+
}
|
|
3820
|
+
syncToolbarViewDisplay() {
|
|
3821
|
+
const buttons = this.toolbarEl.querySelector(".tnc-toolbar-views");
|
|
3822
|
+
const select = this.toolbarEl.querySelector(".tnc-toolbar-view-select");
|
|
3823
|
+
const start = this.toolbarEl.querySelector(".tnc-toolbar-start");
|
|
3824
|
+
if (!buttons || !select || !start) return;
|
|
3825
|
+
const config = this.opts.toolbar;
|
|
3826
|
+
const mode = typeof config === "object" ? config.viewDisplay ?? "auto" : "auto";
|
|
3827
|
+
buttons.hidden = false;
|
|
3828
|
+
select.hidden = true;
|
|
3829
|
+
buttons.style.display = "flex";
|
|
3830
|
+
select.style.display = "none";
|
|
3831
|
+
const startWidth = Array.from(start.children).reduce((width, child) => width + child.scrollWidth, 0);
|
|
3832
|
+
const availableWidth = Math.min(this.root.clientWidth, this.toolbarEl.clientWidth);
|
|
3833
|
+
const compact = mode === "select" || mode === "auto" && startWidth + buttons.scrollWidth + 32 > availableWidth;
|
|
3834
|
+
buttons.hidden = compact;
|
|
3835
|
+
select.hidden = !compact;
|
|
3836
|
+
buttons.style.display = compact ? "none" : "flex";
|
|
3837
|
+
select.style.display = compact ? "" : "none";
|
|
3838
|
+
select.parentElement?.classList.toggle("tnc-toolbar-end--compact", compact);
|
|
3669
3839
|
}
|
|
3670
3840
|
applyChange(change) {
|
|
3671
3841
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3730,7 +3900,7 @@ var Calendar = class {
|
|
|
3730
3900
|
const s = wallTime(n.startMs, this.zone);
|
|
3731
3901
|
const start = this.fmt.time(s.minute, true);
|
|
3732
3902
|
if (variant === "dot" || variant === "bar" || variant === "indicator") return { start: s.day === day ? start : "", end: "" };
|
|
3733
|
-
if (n.startMs === n.endMs) return { start, end: "" };
|
|
3903
|
+
if (n.startMs === n.endMs || n.item.timeDisplay === "start") return { start, end: "" };
|
|
3734
3904
|
return { start, end: this.fmt.time(wallTime(n.endMs, this.zone).minute, true) };
|
|
3735
3905
|
}
|
|
3736
3906
|
fillTime(el, n, variant, day) {
|
|
@@ -3746,7 +3916,8 @@ var Calendar = class {
|
|
|
3746
3916
|
}
|
|
3747
3917
|
const s = wallTime(n.startMs, this.zone);
|
|
3748
3918
|
const e = wallTime(n.endMs, this.zone);
|
|
3749
|
-
|
|
3919
|
+
const start = `${n.item.title}, ${this.fmt.longDate(s.day)} ${this.fmt.time(s.minute)}`;
|
|
3920
|
+
return n.item.timeDisplay === "start" || n.startMs === n.endMs ? start : `${start} \u2013 ${this.fmt.time(e.minute)}`;
|
|
3750
3921
|
}
|
|
3751
3922
|
defaultContent(meta) {
|
|
3752
3923
|
const { n, variant, day } = meta;
|
|
@@ -3774,7 +3945,7 @@ var Calendar = class {
|
|
|
3774
3945
|
addHandles(el, meta) {
|
|
3775
3946
|
const { n, variant } = meta;
|
|
3776
3947
|
const ports = variant === "timeline" && !meta.mirror && n.id !== EXTERNAL_ID && !!this.opts.timeline?.onLink;
|
|
3777
|
-
if (variant === "dot" || !this.isEditable(n, "duration")) {
|
|
3948
|
+
if (variant === "dot" || !this.isEditable(n, "duration", true)) {
|
|
3778
3949
|
if (ports) this.addPorts(el, n);
|
|
3779
3950
|
return;
|
|
3780
3951
|
}
|
|
@@ -3798,6 +3969,38 @@ var Calendar = class {
|
|
|
3798
3969
|
}
|
|
3799
3970
|
}
|
|
3800
3971
|
/* ============================================================== popover */
|
|
3972
|
+
onMoreGeometryChange = () => this.positionMore();
|
|
3973
|
+
listenMoreGeometry(on) {
|
|
3974
|
+
const method = on ? "addEventListener" : "removeEventListener";
|
|
3975
|
+
this.doc[method]("scroll", this.onMoreGeometryChange, true);
|
|
3976
|
+
this.win[method]("resize", this.onMoreGeometryChange);
|
|
3977
|
+
this.win.visualViewport?.[method]("resize", this.onMoreGeometryChange);
|
|
3978
|
+
this.win.visualViewport?.[method]("scroll", this.onMoreGeometryChange);
|
|
3979
|
+
}
|
|
3980
|
+
positionMore() {
|
|
3981
|
+
const popover = this.popover;
|
|
3982
|
+
if (!popover) return;
|
|
3983
|
+
if (!popover.anchor.isConnected) {
|
|
3984
|
+
this.closeMore();
|
|
3985
|
+
return;
|
|
3986
|
+
}
|
|
3987
|
+
const viewport = this.win.visualViewport;
|
|
3988
|
+
const root = this.root.getBoundingClientRect();
|
|
3989
|
+
const anchor = popover.anchor.getBoundingClientRect();
|
|
3990
|
+
const leftEdge = Math.max(root.left, viewport?.offsetLeft ?? 0) + 4;
|
|
3991
|
+
const topEdge = Math.max(root.top, viewport?.offsetTop ?? 0) + 4;
|
|
3992
|
+
const rightEdge = Math.min(root.right, (viewport?.offsetLeft ?? 0) + (viewport?.width ?? this.win.innerWidth)) - 4;
|
|
3993
|
+
const bottomEdge = Math.min(root.bottom, (viewport?.offsetTop ?? 0) + (viewport?.height ?? this.win.innerHeight)) - 4;
|
|
3994
|
+
if (rightEdge <= leftEdge || bottomEdge <= topEdge) {
|
|
3995
|
+
this.closeMore();
|
|
3996
|
+
return;
|
|
3997
|
+
}
|
|
3998
|
+
const width = Math.min(360, rightEdge - leftEdge);
|
|
3999
|
+
setStyle(popover.el, { width, "max-height": Math.min(420, bottomEdge - topEdge) });
|
|
4000
|
+
const left = Math.max(leftEdge, Math.min(anchor.left - 12, rightEdge - width));
|
|
4001
|
+
const top = Math.max(topEdge, Math.min(anchor.top - 40, bottomEdge - popover.el.offsetHeight));
|
|
4002
|
+
setStyle(popover.el, { left: left - root.left, top: top - root.top });
|
|
4003
|
+
}
|
|
3801
4004
|
refreshMore() {
|
|
3802
4005
|
const p = this.popover;
|
|
3803
4006
|
if (!p) return;
|
|
@@ -3807,6 +4010,7 @@ var Calendar = class {
|
|
|
3807
4010
|
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 });
|
|
3808
4011
|
}
|
|
3809
4012
|
p.pool.end();
|
|
4013
|
+
this.positionMore();
|
|
3810
4014
|
}
|
|
3811
4015
|
closeMore(restoreFocus = false) {
|
|
3812
4016
|
const p = this.popover;
|
|
@@ -3834,6 +4038,7 @@ var Calendar = class {
|
|
|
3834
4038
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
3835
4039
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
3836
4040
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
4041
|
+
this.listenMoreGeometry(true);
|
|
3837
4042
|
}
|
|
3838
4043
|
unbindEvents() {
|
|
3839
4044
|
const r = this.root;
|
|
@@ -3852,6 +4057,7 @@ var Calendar = class {
|
|
|
3852
4057
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
3853
4058
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
3854
4059
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
4060
|
+
this.listenMoreGeometry(false);
|
|
3855
4061
|
this.listenDoc(false);
|
|
3856
4062
|
}
|
|
3857
4063
|
listenDoc(on) {
|
|
@@ -3867,6 +4073,10 @@ var Calendar = class {
|
|
|
3867
4073
|
return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
|
|
3868
4074
|
}
|
|
3869
4075
|
onDocPointerDown = (ev) => {
|
|
4076
|
+
if (this.cancelledPointerId !== null) {
|
|
4077
|
+
this.cancelledPointerId = null;
|
|
4078
|
+
this.listenDoc(false);
|
|
4079
|
+
}
|
|
3870
4080
|
const t = ev.target;
|
|
3871
4081
|
if (this.popover && !this.popover.el.contains(t) && !this.popover.anchor.contains(t)) this.closeMore();
|
|
3872
4082
|
if (this.selection && !this.root.contains(t)) this.unselect();
|
|
@@ -3874,6 +4084,7 @@ var Calendar = class {
|
|
|
3874
4084
|
onPointerDown = (ev) => {
|
|
3875
4085
|
if (ev.button !== 0 || this.session) return;
|
|
3876
4086
|
const target = ev.target;
|
|
4087
|
+
if (target.closest(".tnc-dg--compact .tnc-dg-day")) return;
|
|
3877
4088
|
if (target.closest("button, a, input, select, textarea, .tnc-toolbar, .tnc-port, [data-tnc-no-drag]")) {
|
|
3878
4089
|
if (!target.closest(".tnc-item")) return;
|
|
3879
4090
|
if (target.closest(".tnc-port, [data-tnc-no-drag], input, select, textarea")) return;
|
|
@@ -3897,6 +4108,7 @@ var Calendar = class {
|
|
|
3897
4108
|
mode = "select";
|
|
3898
4109
|
}
|
|
3899
4110
|
const touch = ev.pointerType === "touch";
|
|
4111
|
+
const overflowOrigin = itemEl?.closest(".tnc-popover") && origin ? wallTime(origin.startMs, this.zone) : null;
|
|
3900
4112
|
const s = {
|
|
3901
4113
|
mode,
|
|
3902
4114
|
pointerId: ev.pointerId,
|
|
@@ -3905,7 +4117,7 @@ var Calendar = class {
|
|
|
3905
4117
|
y0: ev.clientY,
|
|
3906
4118
|
active: false,
|
|
3907
4119
|
origin,
|
|
3908
|
-
grab: this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
4120
|
+
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,
|
|
3909
4121
|
proposal: null,
|
|
3910
4122
|
denied: false,
|
|
3911
4123
|
timer: null,
|
|
@@ -3930,16 +4142,79 @@ var Calendar = class {
|
|
|
3930
4142
|
};
|
|
3931
4143
|
activate(s) {
|
|
3932
4144
|
s.active = true;
|
|
4145
|
+
if (s.lastEvent) this.leaveHover(s.lastEvent);
|
|
3933
4146
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
3934
4147
|
s.timer = null;
|
|
3935
4148
|
if (s.origin) {
|
|
3936
4149
|
this.draggingId = s.origin.id;
|
|
3937
|
-
for (const el of this.elementsFor(s.origin.id))
|
|
4150
|
+
for (const el of this.elementsFor(s.origin.id)) {
|
|
4151
|
+
if (this.opts.view !== "list" && s.mode === "move") {
|
|
4152
|
+
const source = el.cloneNode(true);
|
|
4153
|
+
source.classList.remove("is-selected");
|
|
4154
|
+
source.classList.add("tnc-drag-source");
|
|
4155
|
+
source.setAttribute("aria-hidden", "true");
|
|
4156
|
+
source.inert = true;
|
|
4157
|
+
for (const node of [source, ...source.querySelectorAll("[id], [data-id], [data-task-path]")]) {
|
|
4158
|
+
node.removeAttribute("id");
|
|
4159
|
+
node.removeAttribute("data-id");
|
|
4160
|
+
node.removeAttribute("data-task-path");
|
|
4161
|
+
}
|
|
4162
|
+
el.before(source);
|
|
4163
|
+
this.dragSources.push(source);
|
|
4164
|
+
}
|
|
4165
|
+
if (this.opts.view === "list" && !this.listDragPreview && s.mode === "move") {
|
|
4166
|
+
const bounds = el.getBoundingClientRect();
|
|
4167
|
+
const preview = el.cloneNode(true);
|
|
4168
|
+
preview.classList.add("tnc-drag-preview", "is-dragging");
|
|
4169
|
+
preview.removeAttribute("data-id");
|
|
4170
|
+
preview.removeAttribute("data-task-path");
|
|
4171
|
+
preview.setAttribute("aria-hidden", "true");
|
|
4172
|
+
preview.inert = true;
|
|
4173
|
+
preview.style.width = `${bounds.width}px`;
|
|
4174
|
+
this.root.append(preview);
|
|
4175
|
+
this.listDragPreview = { element: preview, offsetX: s.x0 - bounds.left, offsetY: s.y0 - bounds.top, width: bounds.width, height: bounds.height };
|
|
4176
|
+
}
|
|
4177
|
+
el.classList.add("is-dragging");
|
|
4178
|
+
}
|
|
3938
4179
|
}
|
|
3939
4180
|
this.closeMore();
|
|
3940
4181
|
if (s.mode !== "select") this.unselectSilently();
|
|
3941
4182
|
this.root.classList.add("tnc-dragging", `tnc-dragging-${s.mode}`);
|
|
3942
4183
|
if (s.lastEvent) this.track(s, s.lastEvent);
|
|
4184
|
+
this.startDragScroll();
|
|
4185
|
+
}
|
|
4186
|
+
startDragScroll() {
|
|
4187
|
+
if (this.dragScrollFrame !== null) return;
|
|
4188
|
+
const tick = () => {
|
|
4189
|
+
this.dragScrollFrame = null;
|
|
4190
|
+
const session = this.session;
|
|
4191
|
+
const pointer = session?.lastEvent;
|
|
4192
|
+
if (!session?.active || !pointer) return;
|
|
4193
|
+
const origin = session.origin ? this.elementsFor(session.origin.id)[0] : this.viewHost;
|
|
4194
|
+
const underPointer = this.doc.elementFromPoint(pointer.clientX, pointer.clientY);
|
|
4195
|
+
let moved = false;
|
|
4196
|
+
if (origin && underPointer && this.root.contains(underPointer)) {
|
|
4197
|
+
for (const axis of ["x", "y"]) {
|
|
4198
|
+
const scroller = scrollerOf(origin, axis);
|
|
4199
|
+
if (!scroller) continue;
|
|
4200
|
+
const viewport = scroller === this.doc.scrollingElement ? { left: 0, top: 0, right: this.win.innerWidth, bottom: this.win.innerHeight } : scroller.getBoundingClientRect();
|
|
4201
|
+
const coordinate = axis === "x" ? pointer.clientX : pointer.clientY;
|
|
4202
|
+
const lower = Math.max(0, axis === "x" ? viewport.left : viewport.top);
|
|
4203
|
+
const upper = Math.min(axis === "x" ? this.win.innerWidth : this.win.innerHeight, axis === "x" ? viewport.right : viewport.bottom);
|
|
4204
|
+
if (coordinate < lower || coordinate > upper) continue;
|
|
4205
|
+
const delta = coordinate < lower + 32 ? -Math.min(12, (lower + 32 - coordinate) / 2) : coordinate > upper - 32 ? Math.min(12, (coordinate - upper + 32) / 2) : 0;
|
|
4206
|
+
if (!delta) continue;
|
|
4207
|
+
if (!this.dragScrollOrigins.has(scroller)) this.dragScrollOrigins.set(scroller, { top: scroller.scrollTop, left: scroller.scrollLeft });
|
|
4208
|
+
const before = axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
|
|
4209
|
+
if (axis === "x") scroller.scrollLeft += delta;
|
|
4210
|
+
else scroller.scrollTop += delta;
|
|
4211
|
+
moved ||= before !== (axis === "x" ? scroller.scrollLeft : scroller.scrollTop);
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
if (moved) this.track(session, pointer);
|
|
4215
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4216
|
+
};
|
|
4217
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
3943
4218
|
}
|
|
3944
4219
|
onTouchMove = (ev) => {
|
|
3945
4220
|
if (this.session?.active) ev.preventDefault();
|
|
@@ -3969,6 +4244,12 @@ var Calendar = class {
|
|
|
3969
4244
|
this.track(s, ev);
|
|
3970
4245
|
};
|
|
3971
4246
|
onPointerUp = (ev) => {
|
|
4247
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
4248
|
+
this.cancelledPointerId = null;
|
|
4249
|
+
this.suppressNextClick();
|
|
4250
|
+
this.listenDoc(false);
|
|
4251
|
+
return;
|
|
4252
|
+
}
|
|
3972
4253
|
const s = this.session;
|
|
3973
4254
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
3974
4255
|
if (!s.active) {
|
|
@@ -3981,28 +4262,49 @@ var Calendar = class {
|
|
|
3981
4262
|
return;
|
|
3982
4263
|
}
|
|
3983
4264
|
this.suppressNextClick();
|
|
4265
|
+
this.track(s, ev);
|
|
3984
4266
|
this.endSession(ev, false);
|
|
3985
4267
|
};
|
|
3986
4268
|
onPointerCancel = (ev) => {
|
|
4269
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
4270
|
+
this.cancelledPointerId = null;
|
|
4271
|
+
this.listenDoc(false);
|
|
4272
|
+
}
|
|
3987
4273
|
if (this.session && ev.pointerId === this.session.pointerId) this.endSession(null, true);
|
|
3988
4274
|
};
|
|
3989
4275
|
onDocKeyDown = (ev) => {
|
|
3990
4276
|
if (ev.key === "Escape" && this.session) {
|
|
3991
4277
|
ev.preventDefault();
|
|
3992
4278
|
ev.stopPropagation();
|
|
4279
|
+
const pointerId = this.session.active ? this.session.pointerId : null;
|
|
3993
4280
|
this.endSession(null, true);
|
|
4281
|
+
if (pointerId !== null) {
|
|
4282
|
+
this.cancelledPointerId = pointerId;
|
|
4283
|
+
this.listenDoc(true);
|
|
4284
|
+
}
|
|
3994
4285
|
}
|
|
3995
4286
|
};
|
|
3996
4287
|
/** Proposal for the current pointer position. */
|
|
3997
4288
|
track(s, ev) {
|
|
4289
|
+
if (this.listDragPreview) {
|
|
4290
|
+
const preview = this.listDragPreview;
|
|
4291
|
+
const bounds = this.root.getBoundingClientRect();
|
|
4292
|
+
preview.element.style.left = `${Math.max(8, Math.min(this.win.innerWidth - preview.width - 8, ev.clientX - preview.offsetX)) - bounds.left}px`;
|
|
4293
|
+
preview.element.style.top = `${Math.max(8, Math.min(this.win.innerHeight - preview.height - 8, ev.clientY - preview.offsetY)) - bounds.top}px`;
|
|
4294
|
+
}
|
|
3998
4295
|
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
3999
4296
|
if (s.mode === "external") {
|
|
4000
4297
|
this.trackExternal(s, hit);
|
|
4001
4298
|
return;
|
|
4002
4299
|
}
|
|
4003
4300
|
if (!s.grab) s.grab = hit;
|
|
4004
|
-
if (!hit || !s.grab)
|
|
4301
|
+
if (!hit || !s.grab) {
|
|
4302
|
+
this.invalidateProposal(s);
|
|
4303
|
+
return;
|
|
4304
|
+
}
|
|
4005
4305
|
if (s.mode === "select") {
|
|
4306
|
+
s.denied = false;
|
|
4307
|
+
this.root.classList.remove("tnc-drop-denied");
|
|
4006
4308
|
const sel = this.selectionFor(s.grab, hit);
|
|
4007
4309
|
if (!sel) return;
|
|
4008
4310
|
this.selection = sel;
|
|
@@ -4011,25 +4313,40 @@ var Calendar = class {
|
|
|
4011
4313
|
}
|
|
4012
4314
|
const origin = s.origin;
|
|
4013
4315
|
const p = s.mode === "move" ? this.moveProposal(origin, s.grab, hit) : this.resizeProposal(origin, hit, s.mode === "resize-start");
|
|
4014
|
-
if (!p)
|
|
4316
|
+
if (!p) {
|
|
4317
|
+
this.invalidateProposal(s);
|
|
4318
|
+
return;
|
|
4319
|
+
}
|
|
4015
4320
|
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;
|
|
4016
4321
|
s.denied = !allowed;
|
|
4017
4322
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
4018
|
-
if (!allowed
|
|
4323
|
+
if (!allowed) {
|
|
4324
|
+
this.invalidateProposal(s);
|
|
4325
|
+
return;
|
|
4326
|
+
}
|
|
4327
|
+
if (sameProposal(p, s.proposal)) return;
|
|
4019
4328
|
s.proposal = p;
|
|
4020
4329
|
this.dragRow = p.rowId !== void 0 ? { id: origin.id, rowId: p.rowId } : null;
|
|
4021
|
-
this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4330
|
+
if (this.opts.view !== "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4331
|
+
}
|
|
4332
|
+
invalidateProposal(s) {
|
|
4333
|
+
const hadProposal = s.proposal !== null;
|
|
4334
|
+
s.proposal = null;
|
|
4335
|
+
s.hit = null;
|
|
4336
|
+
s.denied = true;
|
|
4337
|
+
this.dragRow = null;
|
|
4338
|
+
this.root.classList.add("tnc-drop-denied");
|
|
4339
|
+
this.strip?.markTarget(null);
|
|
4340
|
+
this.unselectSilently();
|
|
4341
|
+
if (s.mode === "external") this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
4342
|
+
else if (hadProposal && s.origin) this.applyChange(this.store.upsert([s.origin.item]));
|
|
4022
4343
|
}
|
|
4023
4344
|
trackExternal(s, raw) {
|
|
4024
4345
|
const src = s.source;
|
|
4025
4346
|
const hit = raw?.kind === "timeline" && raw.area !== "track" && raw.area !== "header" ? null : raw;
|
|
4026
4347
|
s.hit = hit;
|
|
4027
4348
|
if (!hit) {
|
|
4028
|
-
|
|
4029
|
-
s.proposal = null;
|
|
4030
|
-
this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
4031
|
-
}
|
|
4032
|
-
this.unselectSilently();
|
|
4349
|
+
this.invalidateProposal(s);
|
|
4033
4350
|
return;
|
|
4034
4351
|
}
|
|
4035
4352
|
const dur = src.durationMinutes ?? this.opts.defaultTimedMinutes;
|
|
@@ -4048,6 +4365,10 @@ var Calendar = class {
|
|
|
4048
4365
|
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;
|
|
4049
4366
|
s.denied = !allowed;
|
|
4050
4367
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
4368
|
+
if (!allowed) {
|
|
4369
|
+
this.invalidateProposal(s);
|
|
4370
|
+
return;
|
|
4371
|
+
}
|
|
4051
4372
|
if (sameProposal(p, s.proposal)) return;
|
|
4052
4373
|
s.proposal = p;
|
|
4053
4374
|
if (hit.kind === "timeline" && p.rowId === void 0) {
|
|
@@ -4240,7 +4561,18 @@ var Calendar = class {
|
|
|
4240
4561
|
endSession(ev, cancel) {
|
|
4241
4562
|
const s = this.session;
|
|
4242
4563
|
if (!s) return;
|
|
4564
|
+
if (this.dragScrollFrame !== null) this.win.cancelAnimationFrame(this.dragScrollFrame);
|
|
4565
|
+
this.dragScrollFrame = null;
|
|
4566
|
+
if (cancel || s.denied) for (const [scroller, position] of this.dragScrollOrigins) {
|
|
4567
|
+
scroller.scrollTop = position.top;
|
|
4568
|
+
scroller.scrollLeft = position.left;
|
|
4569
|
+
}
|
|
4570
|
+
this.dragScrollOrigins.clear();
|
|
4243
4571
|
this.session = null;
|
|
4572
|
+
this.listDragPreview?.element.remove();
|
|
4573
|
+
this.listDragPreview = null;
|
|
4574
|
+
for (const source of this.dragSources) source.remove();
|
|
4575
|
+
this.dragSources = [];
|
|
4244
4576
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
4245
4577
|
this.listenDoc(false);
|
|
4246
4578
|
this.root.classList.remove(
|
|
@@ -4309,10 +4641,16 @@ var Calendar = class {
|
|
|
4309
4641
|
revert();
|
|
4310
4642
|
return;
|
|
4311
4643
|
}
|
|
4644
|
+
if (this.opts.view === "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4312
4645
|
this.flush();
|
|
4313
4646
|
this.commit(s.mode === "move" ? "drop" : "resize", origin, p, ev, revert);
|
|
4314
4647
|
}
|
|
4315
4648
|
commit(kind, origin, p, ev, revert) {
|
|
4649
|
+
let reverted = false;
|
|
4650
|
+
const mark = (state) => {
|
|
4651
|
+
this.itemWriteStates.set(origin.id, state);
|
|
4652
|
+
for (const element of this.elementsFor(origin.id)) this.applyItemWriteState(element, origin.id);
|
|
4653
|
+
};
|
|
4316
4654
|
const arg = {
|
|
4317
4655
|
item: origin.item,
|
|
4318
4656
|
start: new Date(p.startMs),
|
|
@@ -4328,19 +4666,32 @@ var Calendar = class {
|
|
|
4328
4666
|
jsEvent: ev,
|
|
4329
4667
|
source: ev && "key" in ev ? "keyboard" : "pointer",
|
|
4330
4668
|
view: this.view,
|
|
4331
|
-
revert
|
|
4669
|
+
revert: () => {
|
|
4670
|
+
reverted = true;
|
|
4671
|
+
revert();
|
|
4672
|
+
mark("failed");
|
|
4673
|
+
}
|
|
4332
4674
|
};
|
|
4333
4675
|
const handler = kind === "drop" ? this.opts.onItemDrop : this.opts.onItemResize;
|
|
4334
4676
|
try {
|
|
4677
|
+
mark("pending");
|
|
4335
4678
|
const res = handler?.(arg);
|
|
4336
4679
|
if (res && typeof res.then === "function") {
|
|
4337
|
-
Promise.resolve(res).
|
|
4338
|
-
|
|
4680
|
+
void Promise.resolve(res).then(() => {
|
|
4681
|
+
if (!reverted) mark("changed");
|
|
4682
|
+
}, () => arg.revert());
|
|
4683
|
+
} else if (!reverted) mark("changed");
|
|
4339
4684
|
} catch (err) {
|
|
4340
|
-
revert();
|
|
4685
|
+
arg.revert();
|
|
4341
4686
|
throw err;
|
|
4342
4687
|
}
|
|
4343
4688
|
}
|
|
4689
|
+
applyItemWriteState(element, id) {
|
|
4690
|
+
const state = this.itemWriteStates.get(id);
|
|
4691
|
+
for (const name of ["pending", "failed", "changed"]) element.classList.toggle(`is-write-${name}`, state === name);
|
|
4692
|
+
if (state === "pending") element.setAttribute("aria-busy", "true");
|
|
4693
|
+
else element.removeAttribute("aria-busy");
|
|
4694
|
+
}
|
|
4344
4695
|
/* --------------------------------------------------------- click / keys */
|
|
4345
4696
|
/** Runs in the capture phase so the click ending a drag never reaches content inside the calendar, React portals included. */
|
|
4346
4697
|
onClickCapture = (ev) => {
|
|
@@ -4350,6 +4701,12 @@ var Calendar = class {
|
|
|
4350
4701
|
ev.preventDefault();
|
|
4351
4702
|
};
|
|
4352
4703
|
onClick = (ev) => {
|
|
4704
|
+
const compactDay = ev.target.closest?.(".tnc-dg--compact .tnc-dg-day");
|
|
4705
|
+
if (compactDay && this.root.contains(compactDay) && !ev.target.closest(".tnc-navlink")) {
|
|
4706
|
+
const day = parseDay(compactDay.dataset.date ?? "");
|
|
4707
|
+
if (day !== null) this.openMore(day, compactDay, ev);
|
|
4708
|
+
return;
|
|
4709
|
+
}
|
|
4353
4710
|
const yearDay = ev.target.closest?.(".tnc-ym-day");
|
|
4354
4711
|
if (yearDay && this.root.contains(yearDay)) {
|
|
4355
4712
|
const day = parseDay(yearDay.dataset.date ?? "");
|
|
@@ -4408,6 +4765,25 @@ var Calendar = class {
|
|
|
4408
4765
|
if (el && meta) this.opts.onItemHover?.({ ...this.itemArg(el, meta), jsEvent: ev, entering: false });
|
|
4409
4766
|
}
|
|
4410
4767
|
onKeyDown = (ev) => {
|
|
4768
|
+
if (this.popover && !ev.altKey && !ev.ctrlKey && !ev.metaKey && ["ArrowDown", "ArrowUp", "Home", "End"].includes(ev.key)) {
|
|
4769
|
+
const rows = [...this.popover.el.querySelectorAll(".tnc-item")];
|
|
4770
|
+
const index = rows.indexOf(this.doc.activeElement);
|
|
4771
|
+
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;
|
|
4772
|
+
ev.preventDefault();
|
|
4773
|
+
ev.stopPropagation();
|
|
4774
|
+
rows[next]?.focus();
|
|
4775
|
+
rows[next]?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
4776
|
+
return;
|
|
4777
|
+
}
|
|
4778
|
+
if (ev.key === "Tab" && this.popover) {
|
|
4779
|
+
const controls = [...this.popover.el.querySelectorAll('button, a[href], [tabindex="0"]')];
|
|
4780
|
+
const index = controls.indexOf(this.doc.activeElement);
|
|
4781
|
+
const next = ev.shiftKey ? index <= 0 ? controls.length - 1 : index - 1 : (index + 1) % controls.length;
|
|
4782
|
+
ev.preventDefault();
|
|
4783
|
+
ev.stopPropagation();
|
|
4784
|
+
controls[next]?.focus();
|
|
4785
|
+
return;
|
|
4786
|
+
}
|
|
4411
4787
|
if (ev.key === "Escape" && this.popover) {
|
|
4412
4788
|
ev.preventDefault();
|
|
4413
4789
|
ev.stopPropagation();
|
|
@@ -4419,7 +4795,8 @@ var Calendar = class {
|
|
|
4419
4795
|
const day = parseDay(target.dataset.date ?? "");
|
|
4420
4796
|
if (day !== null) {
|
|
4421
4797
|
ev.preventDefault();
|
|
4422
|
-
|
|
4798
|
+
if (target.closest(".tnc-dg--compact")) this.openMore(day, target, ev);
|
|
4799
|
+
else this.clickSlot({ kind: "day", day }, ev, true);
|
|
4423
4800
|
}
|
|
4424
4801
|
return;
|
|
4425
4802
|
}
|
|
@@ -4507,9 +4884,13 @@ var Calendar = class {
|
|
|
4507
4884
|
const dt = ev.dataTransfer;
|
|
4508
4885
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
4509
4886
|
ev.preventDefault();
|
|
4887
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4888
|
+
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") {
|
|
4889
|
+
dt.dropEffect = "none";
|
|
4890
|
+
this.unselectSilently();
|
|
4891
|
+
return;
|
|
4892
|
+
}
|
|
4510
4893
|
dt.dropEffect = "copy";
|
|
4511
|
-
const hit = this.current.hit(ev.clientX, ev.clientY);
|
|
4512
|
-
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
4513
4894
|
const sel = this.selectionFor(hit, hit);
|
|
4514
4895
|
if (!sel) return;
|
|
4515
4896
|
this.selection = sel;
|
|
@@ -4523,7 +4904,7 @@ var Calendar = class {
|
|
|
4523
4904
|
const dt = ev.dataTransfer;
|
|
4524
4905
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
4525
4906
|
ev.preventDefault();
|
|
4526
|
-
const hit = this.
|
|
4907
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4527
4908
|
this.unselectSilently();
|
|
4528
4909
|
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
4529
4910
|
const sel = this.selectionFor(hit, hit);
|
|
@@ -4554,7 +4935,7 @@ function scrollerOf(el, axis) {
|
|
|
4554
4935
|
return doc.scrollingElement;
|
|
4555
4936
|
}
|
|
4556
4937
|
function rangeMoved(prev, next) {
|
|
4557
|
-
return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.endDay !== next.dataRange.endDay;
|
|
4938
|
+
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;
|
|
4558
4939
|
}
|
|
4559
4940
|
|
|
4560
4941
|
// src/react/index.tsx
|