@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/index.cjs
CHANGED
|
@@ -953,6 +953,7 @@ var DEFAULT_TEXT = {
|
|
|
953
953
|
allDay: "All day",
|
|
954
954
|
noItems: "Nothing scheduled",
|
|
955
955
|
more: (n) => `+${n} more`,
|
|
956
|
+
appointments: (n) => `${n} appointments`,
|
|
956
957
|
itemCount: (n) => n === 1 ? "1 item" : `${n} items`,
|
|
957
958
|
weekNumber: (n) => `W${n}`,
|
|
958
959
|
views: { week: "Week", day: "Day", days: "Days", month: "Month", list: "List", year: "Year", timeline: "Timeline" },
|
|
@@ -1022,6 +1023,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
1022
1023
|
"dateStrip",
|
|
1023
1024
|
"listLeadTitle",
|
|
1024
1025
|
"listAddText",
|
|
1026
|
+
"listEmptyContent",
|
|
1025
1027
|
"monthDisplay",
|
|
1026
1028
|
"timeZone",
|
|
1027
1029
|
"locale",
|
|
@@ -1072,8 +1074,9 @@ var DayGridView = class {
|
|
|
1072
1074
|
constructor(ctx) {
|
|
1073
1075
|
this.ctx = ctx;
|
|
1074
1076
|
const { doc, opts, days } = ctx;
|
|
1075
|
-
this.dotsMode = opts.monthDisplay === "dots";
|
|
1077
|
+
this.dotsMode = opts.monthDisplay === "dots" || opts.monthDisplay === "chips";
|
|
1076
1078
|
this.el = h(doc, "div", this.dotsMode ? "tnc-dg tnc-dg--dots" : "tnc-dg");
|
|
1079
|
+
this.el.classList.toggle("tnc-dg--chips", opts.monthDisplay === "chips");
|
|
1077
1080
|
this.scroller = h(doc, "div", "tnc-dg-scroll");
|
|
1078
1081
|
this.grid = h(doc, "div", "tnc-dg-grid", { role: "grid", "aria-label": ctx.view.title });
|
|
1079
1082
|
const perWeek = this.daysPerWeek();
|
|
@@ -1103,14 +1106,14 @@ var DayGridView = class {
|
|
|
1103
1106
|
const cell = h(doc, "div", cls.join(" "), { role: "gridcell", "aria-label": ctx.fmt.longDate(d), "data-date": arg.dateStr });
|
|
1104
1107
|
if (this.dotsMode) cell.tabIndex = 0;
|
|
1105
1108
|
const num2 = ctx.fmt.dayNumber(d);
|
|
1106
|
-
const label = ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num2}` : num2;
|
|
1109
|
+
const label = !this.dotsMode && ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num2}` : num2;
|
|
1107
1110
|
cell.append(
|
|
1108
1111
|
h(
|
|
1109
1112
|
doc,
|
|
1110
1113
|
"div",
|
|
1111
1114
|
"tnc-dg-top",
|
|
1112
1115
|
null,
|
|
1113
|
-
opts.navLinks ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
1116
|
+
opts.navLinks && !this.dotsMode ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
1114
1117
|
)
|
|
1115
1118
|
);
|
|
1116
1119
|
dates.append(cell);
|
|
@@ -1124,20 +1127,20 @@ var DayGridView = class {
|
|
|
1124
1127
|
layer.append(content);
|
|
1125
1128
|
row.append(dates, layer);
|
|
1126
1129
|
this.grid.append(row);
|
|
1127
|
-
const dots =
|
|
1130
|
+
const dots = cells.map((cell) => {
|
|
1128
1131
|
const box = h(doc, "div", "tnc-dg-dots", { "aria-hidden": "true" });
|
|
1129
1132
|
if (opts.dayMaxEvents === false) box.classList.add("is-unlimited");
|
|
1130
1133
|
const count = h(doc, "span", "tnc-dg-dots-more");
|
|
1131
1134
|
cell.append(box);
|
|
1132
1135
|
return { box, pool: new ItemPool(ctx, box), count };
|
|
1133
|
-
})
|
|
1136
|
+
});
|
|
1134
1137
|
this.weeks.push({ days: wdays, row, cells, layer, sel, more, pool: new ItemPool(ctx, content), dots });
|
|
1135
1138
|
}
|
|
1136
1139
|
this.scroller.append(this.grid);
|
|
1137
1140
|
this.el.append(this.scroller);
|
|
1138
1141
|
this.el.classList.toggle("tnc-dg--auto", opts.height === "auto");
|
|
1139
1142
|
const RO = ctx.win.ResizeObserver;
|
|
1140
|
-
if (RO
|
|
1143
|
+
if (RO) {
|
|
1141
1144
|
this.resizeObs = new RO(() => {
|
|
1142
1145
|
if (this.sizeKey() !== this.lastHeights) this.render(null);
|
|
1143
1146
|
});
|
|
@@ -1168,14 +1171,35 @@ var DayGridView = class {
|
|
|
1168
1171
|
}
|
|
1169
1172
|
render(_change) {
|
|
1170
1173
|
const { ctx } = this;
|
|
1174
|
+
this.dotsMode = ctx.opts.monthDisplay !== "full" || this.grid.clientWidth / this.daysPerWeek() < 72;
|
|
1175
|
+
this.el.classList.toggle("tnc-dg--dots", this.dotsMode);
|
|
1176
|
+
this.el.classList.toggle("tnc-dg--compact", this.dotsMode && ctx.opts.monthDisplay === "full");
|
|
1177
|
+
this.el.classList.toggle("tnc-dg--chips", ctx.opts.monthDisplay === "chips" && this.weeks[0].dots[0].box.clientHeight >= 44);
|
|
1171
1178
|
const cs = ctx.win.getComputedStyle(this.el);
|
|
1172
1179
|
this.barPx = px(cs, "--tnc-bar-h", 20);
|
|
1173
1180
|
this.topPx = this.weeks[0]?.cells[0]?.querySelector(".tnc-dg-top")?.offsetHeight || px(cs, "--tnc-dg-top-h", 24);
|
|
1174
1181
|
this.morePx = this.measureMore(cs);
|
|
1175
1182
|
this.lastHeights = this.sizeKey();
|
|
1176
1183
|
for (const w of this.weeks) {
|
|
1177
|
-
|
|
1178
|
-
|
|
1184
|
+
for (const cell of w.cells) {
|
|
1185
|
+
if (this.dotsMode) cell.tabIndex = 0;
|
|
1186
|
+
else cell.removeAttribute("tabindex");
|
|
1187
|
+
if (this.dotsMode && ctx.opts.monthDisplay === "full") cell.setAttribute("aria-haspopup", "dialog");
|
|
1188
|
+
else cell.removeAttribute("aria-haspopup");
|
|
1189
|
+
}
|
|
1190
|
+
for (const cell of w.dots) cell.box.hidden = !this.dotsMode;
|
|
1191
|
+
if (this.dotsMode) {
|
|
1192
|
+
w.pool.begin();
|
|
1193
|
+
w.pool.end();
|
|
1194
|
+
w.more.replaceChildren();
|
|
1195
|
+
this.renderDots(w);
|
|
1196
|
+
} else {
|
|
1197
|
+
for (const cell of w.dots) {
|
|
1198
|
+
cell.pool.begin();
|
|
1199
|
+
cell.pool.end();
|
|
1200
|
+
}
|
|
1201
|
+
this.renderWeek(w);
|
|
1202
|
+
}
|
|
1179
1203
|
}
|
|
1180
1204
|
}
|
|
1181
1205
|
/** Height of a "+N more" link as the host styles it (`--tnc-more-h`, or a taller min-height). */
|
|
@@ -1261,19 +1285,28 @@ var DayGridView = class {
|
|
|
1261
1285
|
});
|
|
1262
1286
|
}
|
|
1263
1287
|
w.pool.end();
|
|
1264
|
-
w.more.
|
|
1288
|
+
const existing = new Map(Array.from(w.more.children, (element) => [element.getAttribute("data-day"), element]));
|
|
1265
1289
|
hidden.forEach((items, col) => {
|
|
1266
1290
|
if (!items.length) return;
|
|
1267
1291
|
const day = w.days[col];
|
|
1268
|
-
const
|
|
1292
|
+
const key = String(day);
|
|
1293
|
+
let link = existing.get(key);
|
|
1294
|
+
if (!link) {
|
|
1295
|
+
link = h(ctx.doc, "button", "tnc-more", { type: "button", "aria-haspopup": "dialog", "data-day": key });
|
|
1296
|
+
const anchor = link;
|
|
1297
|
+
link.addEventListener("click", (ev) => ctx.openMore(day, anchor, ev));
|
|
1298
|
+
w.more.append(link);
|
|
1299
|
+
}
|
|
1300
|
+
existing.delete(key);
|
|
1301
|
+
link.setAttribute("aria-label", ctx.text.more(items.length));
|
|
1302
|
+
link.textContent = w.row.clientWidth / n < 72 ? `+${new Intl.NumberFormat(ctx.opts.locale).format(items.length)}` : ctx.text.more(items.length);
|
|
1269
1303
|
setStyle(link, {
|
|
1270
1304
|
left: `calc(${pct(col / n)} + 2px)`,
|
|
1271
1305
|
width: `calc(${pct(1 / n)} - 4px)`,
|
|
1272
1306
|
top: `calc(var(--tnc-dg-top-h) + ${visible(col)} * (var(--tnc-bar-h) + ${BAR_GAP}px))`
|
|
1273
1307
|
});
|
|
1274
|
-
link.addEventListener("click", (ev) => ctx.openMore(day, link, ev));
|
|
1275
|
-
w.more.append(link);
|
|
1276
1308
|
});
|
|
1309
|
+
for (const link of existing.values()) link.remove();
|
|
1277
1310
|
const step = this.barPx + BAR_GAP;
|
|
1278
1311
|
let need = maxLevel >= 0 ? (maxLevel + 1) * step - BAR_GAP : 0;
|
|
1279
1312
|
hidden.forEach((items, col) => {
|
|
@@ -1296,10 +1329,11 @@ var DayGridView = class {
|
|
|
1296
1329
|
const { dayMaxEvents } = ctx.opts;
|
|
1297
1330
|
const first = w.dots[0].box;
|
|
1298
1331
|
const cs = ctx.win.getComputedStyle(first);
|
|
1299
|
-
const
|
|
1332
|
+
const chips = this.el.classList.contains("tnc-dg--chips");
|
|
1333
|
+
const size = chips ? px(cs, "--tnc-chip-h", 14) : px(cs, "--tnc-dot-size", 8);
|
|
1300
1334
|
const gap = px(cs, "--tnc-dot-gap", 3);
|
|
1301
1335
|
const step = size + gap;
|
|
1302
|
-
const perRow = Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
1336
|
+
const perRow = chips ? 1 : Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
1303
1337
|
const rows = Math.max(1, Math.floor((first.clientHeight + gap) / step));
|
|
1304
1338
|
const fits = dayMaxEvents === false ? Infinity : perRow * rows;
|
|
1305
1339
|
const cap = typeof dayMaxEvents === "number" && dayMaxEvents > 0 ? Math.min(dayMaxEvents, fits) : fits;
|
|
@@ -1308,7 +1342,7 @@ var DayGridView = class {
|
|
|
1308
1342
|
w.days.forEach((day, i) => {
|
|
1309
1343
|
const cell = w.dots[i];
|
|
1310
1344
|
const items = this.itemsOn(day);
|
|
1311
|
-
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(
|
|
1345
|
+
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(1, cap - (chips ? 1 : 2)) : Math.max(1, cap);
|
|
1312
1346
|
cell.pool.begin();
|
|
1313
1347
|
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 }));
|
|
1314
1348
|
cell.pool.end();
|
|
@@ -1425,6 +1459,7 @@ var ListView = class {
|
|
|
1425
1459
|
const leadItems = [];
|
|
1426
1460
|
const inLead = /* @__PURE__ */ new Set();
|
|
1427
1461
|
const keep = /* @__PURE__ */ new Set();
|
|
1462
|
+
let itemCount = 0;
|
|
1428
1463
|
let prev = null;
|
|
1429
1464
|
for (const day of days) {
|
|
1430
1465
|
let items = this.itemsOn(day);
|
|
@@ -1438,7 +1473,7 @@ var ListView = class {
|
|
|
1438
1473
|
return false;
|
|
1439
1474
|
});
|
|
1440
1475
|
}
|
|
1441
|
-
if (!items.length && !agenda) continue;
|
|
1476
|
+
if (!items.length && !agenda && opts.listDayCount !== 1) continue;
|
|
1442
1477
|
keep.add(day);
|
|
1443
1478
|
let section = this.sections.get(day);
|
|
1444
1479
|
let pool = this.pools.get(day);
|
|
@@ -1457,6 +1492,7 @@ var ListView = class {
|
|
|
1457
1492
|
this.pools.set(day, pool);
|
|
1458
1493
|
}
|
|
1459
1494
|
section.classList.toggle("is-empty", !items.length);
|
|
1495
|
+
itemCount += items.length;
|
|
1460
1496
|
this.fill(pool, items, () => day);
|
|
1461
1497
|
if (prev ? prev.nextSibling !== section : this.body.firstChild !== section) {
|
|
1462
1498
|
if (prev) prev.after(section);
|
|
@@ -1472,11 +1508,14 @@ var ListView = class {
|
|
|
1472
1508
|
this.sections.delete(day);
|
|
1473
1509
|
}
|
|
1474
1510
|
this.renderLead(leadItems);
|
|
1475
|
-
let empty = this.body.querySelector("
|
|
1476
|
-
if (!
|
|
1511
|
+
let empty = this.body.querySelector(".tnc-list-empty");
|
|
1512
|
+
if (!itemCount && !leadItems.length && (!agenda || opts.listEmptyContent)) {
|
|
1477
1513
|
if (!empty) {
|
|
1478
1514
|
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
1479
|
-
|
|
1515
|
+
const content = ctx.opts.listEmptyContent?.();
|
|
1516
|
+
if (content) empty.replaceChildren(content);
|
|
1517
|
+
if (this.sections.size === 1) this.sections.values().next().value.append(empty);
|
|
1518
|
+
else this.body.prepend(empty);
|
|
1480
1519
|
}
|
|
1481
1520
|
} else empty?.remove();
|
|
1482
1521
|
this.ctx.strip?.draw();
|
|
@@ -1534,7 +1573,7 @@ var ListView = class {
|
|
|
1534
1573
|
const section = day === this.ctx.days[0] && this.lead ? this.lead.section : this.sections.get(day);
|
|
1535
1574
|
if (!section) return;
|
|
1536
1575
|
this.ctx.strip?.measure();
|
|
1537
|
-
section.
|
|
1576
|
+
this.scroller.scrollTop += section.getBoundingClientRect().top - this.scroller.getBoundingClientRect().top;
|
|
1538
1577
|
this.ctx.strip?.setFocus(day);
|
|
1539
1578
|
this.pinned = true;
|
|
1540
1579
|
}
|
|
@@ -1623,8 +1662,7 @@ var DateStrip = class {
|
|
|
1623
1662
|
head.append(this.title, this.todayBtn, prev, next, this.slot);
|
|
1624
1663
|
this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
|
|
1625
1664
|
this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
|
|
1626
|
-
const handle = h(doc, "
|
|
1627
|
-
handle.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
1665
|
+
const handle = h(doc, "div", "tnc-strip-handle", { "aria-hidden": "true" });
|
|
1628
1666
|
this.el.append(head, this.dows, this.grid, handle);
|
|
1629
1667
|
this.grid.addEventListener("click", this.onClick);
|
|
1630
1668
|
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
@@ -1824,6 +1862,8 @@ var TimeGridView = class {
|
|
|
1824
1862
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1825
1863
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1826
1864
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1865
|
+
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1866
|
+
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
1827
1867
|
this.top = h(doc, "div", "tnc-tg-top");
|
|
1828
1868
|
this.headRow = h(doc, "div", "tnc-tg-head tnc-tg-cols", { role: "row" });
|
|
1829
1869
|
this.top.append(this.headRow);
|
|
@@ -1851,7 +1891,7 @@ var TimeGridView = class {
|
|
|
1851
1891
|
} else {
|
|
1852
1892
|
this.body = this.gutter = null;
|
|
1853
1893
|
}
|
|
1854
|
-
this.el.append(this.scroller);
|
|
1894
|
+
this.el.append(this.dayNavigation, this.scroller);
|
|
1855
1895
|
this.build();
|
|
1856
1896
|
if (showGrid && opts.height === "fill") {
|
|
1857
1897
|
const RO = ctx.win.ResizeObserver;
|
|
@@ -1864,6 +1904,7 @@ var TimeGridView = class {
|
|
|
1864
1904
|
ctx;
|
|
1865
1905
|
el;
|
|
1866
1906
|
scroller;
|
|
1907
|
+
dayNavigation;
|
|
1867
1908
|
top;
|
|
1868
1909
|
headRow;
|
|
1869
1910
|
allDayRow;
|
|
@@ -1919,6 +1960,9 @@ var TimeGridView = class {
|
|
|
1919
1960
|
head.append(cap);
|
|
1920
1961
|
}
|
|
1921
1962
|
this.headRow.append(head);
|
|
1963
|
+
const dayButton = h(doc, "button", "tnc-btn", { type: "button", "aria-label": ctx.fmt.longDate(day) }, `${ctx.fmt.weekdayShort(day)} ${ctx.fmt.dayNumber(day)}`);
|
|
1964
|
+
dayButton.addEventListener("click", () => this.revealDay(day));
|
|
1965
|
+
this.dayNavigation.append(dayButton);
|
|
1922
1966
|
opts.dayHeaderDidMount?.({ ...arg, el: head });
|
|
1923
1967
|
const col = h(doc, "div", ["tnc-col", ...ctx.dayClasses(day)].join(" "), { role: "cell", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr });
|
|
1924
1968
|
const bgLayer = h(doc, "div", "tnc-col-bg");
|
|
@@ -1935,6 +1979,7 @@ var TimeGridView = class {
|
|
|
1935
1979
|
this.body?.append(col);
|
|
1936
1980
|
return {
|
|
1937
1981
|
day,
|
|
1982
|
+
collisionColumns: 1,
|
|
1938
1983
|
head,
|
|
1939
1984
|
col,
|
|
1940
1985
|
plan: new ItemPool(ctx, planLayer),
|
|
@@ -1991,12 +2036,17 @@ var TimeGridView = class {
|
|
|
1991
2036
|
/* --------------------------------------------------------------- render */
|
|
1992
2037
|
render(change) {
|
|
1993
2038
|
this.renderAllDay();
|
|
1994
|
-
if (!this.body)
|
|
2039
|
+
if (!this.body) {
|
|
2040
|
+
this.updateDayNavigation();
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
1995
2043
|
const dirty = change ? change.days : null;
|
|
1996
2044
|
for (const c of this.cols) {
|
|
1997
2045
|
if (dirty && !dirty.has(c.day) && !(this.ctx.slot.max > MINUTES_PER_DAY && dirty.has(c.day + 1))) continue;
|
|
1998
2046
|
this.renderColumn(c);
|
|
1999
2047
|
}
|
|
2048
|
+
this.el.style.setProperty("--tnc-collision-columns", String(Math.max(1, ...this.cols.map((column) => column.collisionColumns))));
|
|
2049
|
+
this.updateDayNavigation();
|
|
2000
2050
|
this.renderCapacity();
|
|
2001
2051
|
this.tick();
|
|
2002
2052
|
}
|
|
@@ -2114,6 +2164,11 @@ var TimeGridView = class {
|
|
|
2114
2164
|
}
|
|
2115
2165
|
const minMinutes = opts.eventMinHeight / this.ppm;
|
|
2116
2166
|
const layout = layoutTimedDay(items, c.day, zone, minMinutes, ctx.order, [slot.min, slot.max]);
|
|
2167
|
+
c.collisionColumns = Math.max(
|
|
2168
|
+
1,
|
|
2169
|
+
...layout.plan.map((placement) => opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols),
|
|
2170
|
+
...layout.actual.filter((placement) => !placement.split).map((placement) => placement.cols)
|
|
2171
|
+
);
|
|
2117
2172
|
const total = this.minutes;
|
|
2118
2173
|
const top = (m) => pct((m - slot.min) / total);
|
|
2119
2174
|
const height = (s) => pct((s.endMin - s.startMin) / total);
|
|
@@ -2125,14 +2180,22 @@ var TimeGridView = class {
|
|
|
2125
2180
|
c.bg.end();
|
|
2126
2181
|
const hasActual = layout.actual.length > 0;
|
|
2127
2182
|
c.col.classList.toggle("has-actual", hasActual);
|
|
2183
|
+
for (const link of c.more.children) link.setAttribute("data-stale", "true");
|
|
2128
2184
|
this.placeLane(c.plan, layout.plan, c, top, height, "plan");
|
|
2129
2185
|
this.placeLane(c.actual, layout.actual, c, top, height, "actual");
|
|
2186
|
+
for (const link of [...c.more.children]) if (link.hasAttribute("data-stale")) link.remove();
|
|
2130
2187
|
}
|
|
2131
2188
|
placeLane(pool, placements, c, top, height, lane) {
|
|
2132
2189
|
const { ctx } = this;
|
|
2133
2190
|
const { opts } = ctx;
|
|
2134
|
-
const
|
|
2135
|
-
const
|
|
2191
|
+
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
2192
|
+
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
2193
|
+
const collapse = placements.some((placement) => {
|
|
2194
|
+
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
2195
|
+
return columns > 1 && columns * readableWidth > laneWidth;
|
|
2196
|
+
});
|
|
2197
|
+
const withMore = lane === "plan" || collapse;
|
|
2198
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2136
2199
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
2137
2200
|
pool.begin();
|
|
2138
2201
|
for (const p of placements) {
|
|
@@ -2158,21 +2221,46 @@ var TimeGridView = class {
|
|
|
2158
2221
|
top: top(p.startMin),
|
|
2159
2222
|
height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
2160
2223
|
...inLane(lane, p.split, left, width),
|
|
2224
|
+
"--tnc-event-readable-width": pct(span / cols / width),
|
|
2161
2225
|
"z-index": String(p.col + 1)
|
|
2162
2226
|
});
|
|
2163
2227
|
}
|
|
2164
2228
|
pool.end();
|
|
2165
2229
|
if (!withMore) return;
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2230
|
+
for (const [cluster, rec] of hiddenByCluster) {
|
|
2231
|
+
const key = `${lane}-${cluster}`;
|
|
2232
|
+
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
2233
|
+
if (!link) {
|
|
2234
|
+
link = h(ctx.doc, "button", "tnc-more tnc-more--stack", { type: "button", "aria-haspopup": "dialog", "data-cluster": key });
|
|
2235
|
+
const anchor = link;
|
|
2236
|
+
link.addEventListener("click", (ev) => ctx.openMore(c.day, anchor, ev, JSON.parse(anchor.dataset.itemIds ?? "[]")));
|
|
2237
|
+
c.more.append(link);
|
|
2238
|
+
}
|
|
2239
|
+
link.removeAttribute("data-stale");
|
|
2240
|
+
link.dataset.itemIds = JSON.stringify(placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.id));
|
|
2241
|
+
link.classList.toggle("tnc-more--collision", collapse);
|
|
2242
|
+
link.textContent = `+${rec.count}`;
|
|
2243
|
+
if (collapse) {
|
|
2244
|
+
const items = placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.item.title);
|
|
2245
|
+
const count = ctx.text.appointments(rec.count);
|
|
2246
|
+
const interval = `${ctx.fmt.time(rec.start)} \u2013 ${ctx.fmt.time(rec.end)}`;
|
|
2247
|
+
link.replaceChildren(
|
|
2248
|
+
h(ctx.doc, "span", "tnc-collision-count", null, String(rec.count)),
|
|
2249
|
+
h(ctx.doc, "span", "tnc-collision-range", null, interval),
|
|
2250
|
+
h(ctx.doc, "span", "tnc-collision-title", { "aria-hidden": "true" }, items.join(" \xB7 "))
|
|
2251
|
+
);
|
|
2252
|
+
link.setAttribute("aria-label", `${count}, ${interval}: ${items.join(", ")}`);
|
|
2253
|
+
link.title = link.getAttribute("aria-label");
|
|
2254
|
+
} else {
|
|
2255
|
+
link.removeAttribute("aria-label");
|
|
2256
|
+
link.removeAttribute("title");
|
|
2257
|
+
}
|
|
2169
2258
|
const cols = maxStack;
|
|
2170
2259
|
setStyle(link, {
|
|
2171
2260
|
top: top(rec.start),
|
|
2261
|
+
height: collapse ? pct((rec.end - rec.start) / this.minutes) : "",
|
|
2172
2262
|
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
2173
2263
|
});
|
|
2174
|
-
link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
|
|
2175
|
-
c.more.append(link);
|
|
2176
2264
|
}
|
|
2177
2265
|
}
|
|
2178
2266
|
meta(s, bg) {
|
|
@@ -2237,12 +2325,10 @@ var TimeGridView = class {
|
|
|
2237
2325
|
const r2 = cells[i2].getBoundingClientRect();
|
|
2238
2326
|
if (x >= r2.left && x < r2.right) return i2;
|
|
2239
2327
|
}
|
|
2240
|
-
const firstR = cells[0]?.getBoundingClientRect();
|
|
2241
|
-
const lastR = cells[cells.length - 1]?.getBoundingClientRect();
|
|
2242
|
-
if (firstR && x < firstR.left) return 0;
|
|
2243
|
-
if (lastR && x >= lastR.right) return cells.length - 1;
|
|
2244
2328
|
return -1;
|
|
2245
2329
|
};
|
|
2330
|
+
const bounds = this.scroller.getBoundingClientRect();
|
|
2331
|
+
if (x < bounds.left || x >= bounds.right || y < bounds.top || y >= bounds.bottom) return null;
|
|
2246
2332
|
if (this.allDayRow) {
|
|
2247
2333
|
const r2 = this.allDayRow.getBoundingClientRect();
|
|
2248
2334
|
if (y >= r2.top && y < r2.bottom || !this.body && y >= r2.top) {
|
|
@@ -2283,6 +2369,26 @@ var TimeGridView = class {
|
|
|
2283
2369
|
}
|
|
2284
2370
|
resize() {
|
|
2285
2371
|
this.fitRows();
|
|
2372
|
+
this.render(null);
|
|
2373
|
+
}
|
|
2374
|
+
revealDay(day) {
|
|
2375
|
+
const column = this.cols.find((candidate) => candidate.day === day);
|
|
2376
|
+
if (!column) return;
|
|
2377
|
+
const bounds = column.head.getBoundingClientRect();
|
|
2378
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2379
|
+
this.scroller.scrollLeft += bounds.left - viewport.left - (viewport.width - bounds.width) / 2;
|
|
2380
|
+
this.updateDayNavigation();
|
|
2381
|
+
}
|
|
2382
|
+
updateDayNavigation() {
|
|
2383
|
+
this.dayNavigation.hidden = this.scroller.scrollWidth <= this.scroller.clientWidth + 1;
|
|
2384
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2385
|
+
this.cols.forEach((column, index) => {
|
|
2386
|
+
const bounds = column.head.getBoundingClientRect();
|
|
2387
|
+
const visible = bounds.left >= viewport.left && bounds.right <= viewport.right;
|
|
2388
|
+
const button = this.dayNavigation.children[index];
|
|
2389
|
+
button?.classList.toggle("is-offscreen", !visible);
|
|
2390
|
+
button?.setAttribute("aria-pressed", String(visible));
|
|
2391
|
+
});
|
|
2286
2392
|
}
|
|
2287
2393
|
scrollToTime(minute) {
|
|
2288
2394
|
if (!this.body) return;
|
|
@@ -2945,6 +3051,14 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2945
3051
|
setScroll(s) {
|
|
2946
3052
|
this.scroller.scrollTop = s.top;
|
|
2947
3053
|
}
|
|
3054
|
+
revealDay(day) {
|
|
3055
|
+
const month = this.cells.find((cell) => cell.day === day)?.el.closest(".tnc-ym");
|
|
3056
|
+
if (!month) return;
|
|
3057
|
+
const bounds = month.getBoundingClientRect();
|
|
3058
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
3059
|
+
if (bounds.top < viewport.top) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
3060
|
+
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.bottom - viewport.bottom;
|
|
3061
|
+
}
|
|
2948
3062
|
elementsFor(_id) {
|
|
2949
3063
|
return [];
|
|
2950
3064
|
}
|
|
@@ -3024,6 +3138,11 @@ var Calendar = class {
|
|
|
3024
3138
|
conflicts = /* @__PURE__ */ new Map();
|
|
3025
3139
|
slot = { min: 0, max: MINUTES_PER_DAY, duration: 30, snap: 30, scroll: 480 };
|
|
3026
3140
|
draggingId = null;
|
|
3141
|
+
listDragPreview = null;
|
|
3142
|
+
dragSources = [];
|
|
3143
|
+
dragScrollFrame = null;
|
|
3144
|
+
dragScrollOrigins = /* @__PURE__ */ new Map();
|
|
3145
|
+
itemWriteStates = /* @__PURE__ */ new Map();
|
|
3027
3146
|
dragRow = null;
|
|
3028
3147
|
strip = null;
|
|
3029
3148
|
stripSize = null;
|
|
@@ -3037,6 +3156,7 @@ var Calendar = class {
|
|
|
3037
3156
|
session = null;
|
|
3038
3157
|
selection = null;
|
|
3039
3158
|
suppressClick = false;
|
|
3159
|
+
cancelledPointerId = null;
|
|
3040
3160
|
selectedIds = /* @__PURE__ */ new Set();
|
|
3041
3161
|
popover = null;
|
|
3042
3162
|
timer = null;
|
|
@@ -3048,8 +3168,13 @@ var Calendar = class {
|
|
|
3048
3168
|
this.resizeObs?.disconnect();
|
|
3049
3169
|
const RO = this.win.ResizeObserver;
|
|
3050
3170
|
if (RO) {
|
|
3051
|
-
this.resizeObs = new RO(() =>
|
|
3171
|
+
this.resizeObs = new RO(() => {
|
|
3172
|
+
this.syncToolbarViewDisplay();
|
|
3173
|
+
this.current?.resize?.();
|
|
3174
|
+
this.positionMore();
|
|
3175
|
+
});
|
|
3052
3176
|
this.resizeObs.observe(this.root);
|
|
3177
|
+
this.resizeObs.observe(this.toolbarEl);
|
|
3053
3178
|
}
|
|
3054
3179
|
}
|
|
3055
3180
|
onAdoptCheck = () => {
|
|
@@ -3068,9 +3193,11 @@ var Calendar = class {
|
|
|
3068
3193
|
if (this.frame !== null) old.cancelAnimationFrame(this.frame);
|
|
3069
3194
|
this.timer = this.frame = null;
|
|
3070
3195
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3196
|
+
this.listenMoreGeometry(false);
|
|
3071
3197
|
this.doc = doc;
|
|
3072
3198
|
this.win = doc.defaultView ?? old;
|
|
3073
3199
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3200
|
+
this.listenMoreGeometry(true);
|
|
3074
3201
|
this.observeResize();
|
|
3075
3202
|
this.rebuild(false);
|
|
3076
3203
|
this.scheduleTick();
|
|
@@ -3174,7 +3301,12 @@ var Calendar = class {
|
|
|
3174
3301
|
gotoDate(date) {
|
|
3175
3302
|
const day = this.toDay(date);
|
|
3176
3303
|
if (day === null) return;
|
|
3304
|
+
const sameMonth = this.opts.view === "month" && startOfMonth(day) === startOfMonth(this.anchor);
|
|
3177
3305
|
this.anchor = day;
|
|
3306
|
+
if (sameMonth) {
|
|
3307
|
+
this.view = { ...this.view, anchor: day };
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3178
3310
|
this.rebuild(true);
|
|
3179
3311
|
}
|
|
3180
3312
|
prev() {
|
|
@@ -3188,6 +3320,7 @@ var Calendar = class {
|
|
|
3188
3320
|
goToday() {
|
|
3189
3321
|
this.anchor = this.todayDay();
|
|
3190
3322
|
this.rebuild(true);
|
|
3323
|
+
if (this.current instanceof TimeGridView) this.current.revealDay(this.anchor);
|
|
3191
3324
|
}
|
|
3192
3325
|
/** The anchor date, as local midnight of that civil day in the calendar zone. */
|
|
3193
3326
|
getDate() {
|
|
@@ -3206,6 +3339,7 @@ var Calendar = class {
|
|
|
3206
3339
|
/** Re-measures (e.g. after the host was shown or resized by a parent). */
|
|
3207
3340
|
updateSize() {
|
|
3208
3341
|
this.current?.resize?.();
|
|
3342
|
+
this.positionMore();
|
|
3209
3343
|
}
|
|
3210
3344
|
getScroll() {
|
|
3211
3345
|
return this.current?.getScroll() ?? { top: 0, left: 0 };
|
|
@@ -3237,7 +3371,10 @@ var Calendar = class {
|
|
|
3237
3371
|
}
|
|
3238
3372
|
const view = this.current;
|
|
3239
3373
|
if (view instanceof TimelineView) view.scrollToInstant(ms, opts.align ?? "center", opts.smooth ?? false);
|
|
3240
|
-
else if (view instanceof TimeGridView
|
|
3374
|
+
else if (view instanceof TimeGridView) {
|
|
3375
|
+
view.revealDay(day);
|
|
3376
|
+
if (p.kind !== "day") view.scrollToTime(wallTime(ms, this.zone).minute);
|
|
3377
|
+
}
|
|
3241
3378
|
}
|
|
3242
3379
|
/** What is under a client point: a time slot, a day, or a timeline row and date. Null outside the view. */
|
|
3243
3380
|
hitTest(clientX, clientY) {
|
|
@@ -3315,7 +3452,8 @@ var Calendar = class {
|
|
|
3315
3452
|
out.push(...classList(this.opts.dayClassNames?.(this.dayArg(day))));
|
|
3316
3453
|
return out;
|
|
3317
3454
|
}
|
|
3318
|
-
isEditable(n, what) {
|
|
3455
|
+
isEditable(n, what, presentation = false) {
|
|
3456
|
+
if (!presentation && this.itemWriteStates.get(n.id) === "pending") return false;
|
|
3319
3457
|
if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
|
|
3320
3458
|
if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
|
|
3321
3459
|
const it = n.item;
|
|
@@ -3355,13 +3493,14 @@ var Calendar = class {
|
|
|
3355
3493
|
if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
|
|
3356
3494
|
if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
|
|
3357
3495
|
const indicator = variant === "indicator";
|
|
3358
|
-
if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
|
|
3496
|
+
if (!preview && !indicator && (this.isEditable(n, "start", true) || this.isEditable(n, "duration", true))) el.classList.add("is-draggable");
|
|
3359
3497
|
if (this.draggingId === n.id) el.classList.add("is-dragging");
|
|
3360
3498
|
if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
|
|
3361
3499
|
el.classList.add(...classList(it.className));
|
|
3362
3500
|
const arg = this.itemArg(el, meta);
|
|
3363
3501
|
el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
|
|
3364
3502
|
el.dataset.id = n.id;
|
|
3503
|
+
this.applyItemWriteState(el, n.id);
|
|
3365
3504
|
if (it.kind) el.dataset.kind = it.kind;
|
|
3366
3505
|
const color = it.color ?? it.borderColor;
|
|
3367
3506
|
if (color) el.style.setProperty("--tnc-c", color);
|
|
@@ -3378,6 +3517,7 @@ var Calendar = class {
|
|
|
3378
3517
|
const custom = variant === "list" ? this.opts.listItemContent?.(arg) : null;
|
|
3379
3518
|
const content = indicator ? null : custom ?? this.opts.itemContent?.(arg);
|
|
3380
3519
|
if (indicator) {
|
|
3520
|
+
if (this.opts.monthDisplay === "chips") el.append(h(this.doc, "span", "tnc-item-title", null, it.title));
|
|
3381
3521
|
} else if (content != null) {
|
|
3382
3522
|
const wrap = h(this.doc, "div", "tnc-item-main");
|
|
3383
3523
|
wrap.append(content);
|
|
@@ -3390,6 +3530,7 @@ var Calendar = class {
|
|
|
3390
3530
|
return el;
|
|
3391
3531
|
}
|
|
3392
3532
|
refreshItemEl(el, meta) {
|
|
3533
|
+
this.applyItemWriteState(el, meta.n.id);
|
|
3393
3534
|
const time = el.querySelector(":scope .tnc-item-time");
|
|
3394
3535
|
if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
|
|
3395
3536
|
el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
|
|
@@ -3412,9 +3553,10 @@ var Calendar = class {
|
|
|
3412
3553
|
if (meta) this.opts.itemWillUnmount?.(this.itemArg(el, meta));
|
|
3413
3554
|
el.remove();
|
|
3414
3555
|
}
|
|
3415
|
-
openMore(day, anchor, jsEvent) {
|
|
3556
|
+
openMore(day, anchor, jsEvent, itemIds) {
|
|
3416
3557
|
jsEvent.stopPropagation();
|
|
3417
|
-
const
|
|
3558
|
+
const group = itemIds ? new Set(itemIds) : null;
|
|
3559
|
+
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));
|
|
3418
3560
|
const shown = new Set(
|
|
3419
3561
|
[...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 ?? "")
|
|
3420
3562
|
);
|
|
@@ -3443,15 +3585,7 @@ var Calendar = class {
|
|
|
3443
3585
|
}
|
|
3444
3586
|
pool.end();
|
|
3445
3587
|
this.popover = { el, pool, anchor, day };
|
|
3446
|
-
|
|
3447
|
-
const ar = anchor.getBoundingClientRect();
|
|
3448
|
-
const w = Math.max(220, ar.width + 24);
|
|
3449
|
-
let left = ar.left - rr.left - 12;
|
|
3450
|
-
left = Math.max(4, Math.min(left, rr.width - w - 4));
|
|
3451
|
-
let top = ar.top - rr.top - 40;
|
|
3452
|
-
const height = el.offsetHeight;
|
|
3453
|
-
top = Math.max(4, Math.min(top, rr.height - height - 4));
|
|
3454
|
-
setStyle(el, { left, top, width: w });
|
|
3588
|
+
this.positionMore();
|
|
3455
3589
|
el.focus();
|
|
3456
3590
|
}
|
|
3457
3591
|
/* =============================================================== render */
|
|
@@ -3575,8 +3709,11 @@ var Calendar = class {
|
|
|
3575
3709
|
this.current.setScroll({ top: keep.top, left: 0 });
|
|
3576
3710
|
this.current.placeInstant(keep.ms, keep.vx);
|
|
3577
3711
|
} else if (scroll) this.current.setScroll(scroll);
|
|
3578
|
-
else if (this.current instanceof TimeGridView)
|
|
3579
|
-
|
|
3712
|
+
else if (this.current instanceof TimeGridView) {
|
|
3713
|
+
this.current.scrollToTime(this.slot.scroll);
|
|
3714
|
+
this.current.revealDay(this.anchor);
|
|
3715
|
+
} else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
3716
|
+
else if (this.current instanceof YearView) this.current.revealDay(this.anchor);
|
|
3580
3717
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
3581
3718
|
}
|
|
3582
3719
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -3602,9 +3739,11 @@ var Calendar = class {
|
|
|
3602
3739
|
}
|
|
3603
3740
|
/** The range, run on to the end of the strip's days so their dots are right. */
|
|
3604
3741
|
dataRangeFor(range) {
|
|
3605
|
-
const
|
|
3606
|
-
|
|
3607
|
-
|
|
3742
|
+
const window = this.strip?.window();
|
|
3743
|
+
const start = window ? Math.min(range.startDay, window.start) : range.startDay;
|
|
3744
|
+
const end = window ? Math.max(range.endDay, window.end) : range.endDay;
|
|
3745
|
+
if (start === range.startDay && end === range.endDay) return range;
|
|
3746
|
+
return { ...range, startDay: start, start: new Date(toInstant(start, 0, this.zone)), endDay: end, end: new Date(toInstant(end, 0, this.zone)) };
|
|
3608
3747
|
}
|
|
3609
3748
|
/** Adds, updates or removes the date strip for the current view. */
|
|
3610
3749
|
syncStrip(navigated) {
|
|
@@ -3651,20 +3790,24 @@ var Calendar = class {
|
|
|
3651
3790
|
stripShown() {
|
|
3652
3791
|
if (!this.strip || this.rebuilding || !this.view) return;
|
|
3653
3792
|
const next = this.dataRangeFor(this.view.range);
|
|
3654
|
-
if (next.endDay === this.view.dataRange.endDay) return;
|
|
3793
|
+
if (next.startDay === this.view.dataRange.startDay && next.endDay === this.view.dataRange.endDay) return;
|
|
3655
3794
|
this.view = { ...this.view, dataRange: next };
|
|
3656
3795
|
this.opts.onRangeChange?.(this.view);
|
|
3657
3796
|
}
|
|
3658
3797
|
/** What is under a point, the date strip included. */
|
|
3659
3798
|
hitAt(x, y) {
|
|
3799
|
+
const target = this.doc.elementFromPoint(x, y);
|
|
3800
|
+
if (!target || !this.root.contains(target)) {
|
|
3801
|
+
this.strip?.markTarget(null);
|
|
3802
|
+
return null;
|
|
3803
|
+
}
|
|
3660
3804
|
const strip = this.strip;
|
|
3661
3805
|
if (strip) {
|
|
3662
|
-
const
|
|
3663
|
-
const day = el ? strip.dayAt(el) : null;
|
|
3806
|
+
const day = strip.dayAt(target);
|
|
3664
3807
|
strip.markTarget(day !== null && this.draggingId ? day : null);
|
|
3665
3808
|
if (day !== null) return { kind: "day", day };
|
|
3666
3809
|
}
|
|
3667
|
-
return this.current?.hit(x, y)
|
|
3810
|
+
return this.current?.el.contains(target) ? this.current.hit(x, y) : null;
|
|
3668
3811
|
}
|
|
3669
3812
|
agendaList() {
|
|
3670
3813
|
return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
|
|
@@ -3747,17 +3890,44 @@ var Calendar = class {
|
|
|
3747
3890
|
);
|
|
3748
3891
|
const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
|
|
3749
3892
|
const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
|
|
3893
|
+
const viewSelect = h(this.doc, "select", "tnc-btn tnc-toolbar-view-select", {
|
|
3894
|
+
"aria-label": this.text.viewHints[this.opts.view] ?? this.text.views[this.opts.view] ?? this.opts.view
|
|
3895
|
+
});
|
|
3750
3896
|
for (const v of views) {
|
|
3751
3897
|
const label = v === "days" ? this.text.views.days ?? `${this.opts.dayCount} days` : this.text.views[v] ?? v;
|
|
3752
3898
|
viewGroup.append(btn(label, this.text.viewHints[v] ?? label, `tnc-btn-view tnc-btn-view-${v}`, () => this.changeView(v), v === this.opts.view));
|
|
3899
|
+
viewSelect.append(h(this.doc, "option", "", { value: v }, label));
|
|
3753
3900
|
}
|
|
3901
|
+
viewSelect.value = this.opts.view;
|
|
3902
|
+
viewSelect.addEventListener("change", () => this.changeView(viewSelect.value));
|
|
3754
3903
|
const start = h(this.doc, "div", "tnc-toolbar-start");
|
|
3755
3904
|
const end = h(this.doc, "div", "tnc-toolbar-end");
|
|
3756
3905
|
if (cfg.extraPlacement === "start") start.append(...extras);
|
|
3757
3906
|
start.append(nav, title);
|
|
3758
|
-
end.append(viewGroup);
|
|
3907
|
+
end.append(viewGroup, viewSelect);
|
|
3759
3908
|
if (cfg.extraPlacement !== "start") end.append(...extras);
|
|
3760
3909
|
this.toolbarEl.append(start, end);
|
|
3910
|
+
this.syncToolbarViewDisplay();
|
|
3911
|
+
}
|
|
3912
|
+
syncToolbarViewDisplay() {
|
|
3913
|
+
const buttons = this.toolbarEl.querySelector(".tnc-toolbar-views");
|
|
3914
|
+
const select = this.toolbarEl.querySelector(".tnc-toolbar-view-select");
|
|
3915
|
+
const start = this.toolbarEl.querySelector(".tnc-toolbar-start");
|
|
3916
|
+
if (!buttons || !select || !start) return;
|
|
3917
|
+
const config = this.opts.toolbar;
|
|
3918
|
+
const mode = typeof config === "object" ? config.viewDisplay ?? "auto" : "auto";
|
|
3919
|
+
buttons.hidden = false;
|
|
3920
|
+
select.hidden = true;
|
|
3921
|
+
buttons.style.display = "flex";
|
|
3922
|
+
select.style.display = "none";
|
|
3923
|
+
const startWidth = Array.from(start.children).reduce((width, child) => width + child.scrollWidth, 0);
|
|
3924
|
+
const availableWidth = Math.min(this.root.clientWidth, this.toolbarEl.clientWidth);
|
|
3925
|
+
const compact = mode === "select" || mode === "auto" && startWidth + buttons.scrollWidth + 32 > availableWidth;
|
|
3926
|
+
buttons.hidden = compact;
|
|
3927
|
+
select.hidden = !compact;
|
|
3928
|
+
buttons.style.display = compact ? "none" : "flex";
|
|
3929
|
+
select.style.display = compact ? "" : "none";
|
|
3930
|
+
select.parentElement?.classList.toggle("tnc-toolbar-end--compact", compact);
|
|
3761
3931
|
}
|
|
3762
3932
|
applyChange(change) {
|
|
3763
3933
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3822,7 +3992,7 @@ var Calendar = class {
|
|
|
3822
3992
|
const s = wallTime(n.startMs, this.zone);
|
|
3823
3993
|
const start = this.fmt.time(s.minute, true);
|
|
3824
3994
|
if (variant === "dot" || variant === "bar" || variant === "indicator") return { start: s.day === day ? start : "", end: "" };
|
|
3825
|
-
if (n.startMs === n.endMs) return { start, end: "" };
|
|
3995
|
+
if (n.startMs === n.endMs || n.item.timeDisplay === "start") return { start, end: "" };
|
|
3826
3996
|
return { start, end: this.fmt.time(wallTime(n.endMs, this.zone).minute, true) };
|
|
3827
3997
|
}
|
|
3828
3998
|
fillTime(el, n, variant, day) {
|
|
@@ -3838,7 +4008,8 @@ var Calendar = class {
|
|
|
3838
4008
|
}
|
|
3839
4009
|
const s = wallTime(n.startMs, this.zone);
|
|
3840
4010
|
const e = wallTime(n.endMs, this.zone);
|
|
3841
|
-
|
|
4011
|
+
const start = `${n.item.title}, ${this.fmt.longDate(s.day)} ${this.fmt.time(s.minute)}`;
|
|
4012
|
+
return n.item.timeDisplay === "start" || n.startMs === n.endMs ? start : `${start} \u2013 ${this.fmt.time(e.minute)}`;
|
|
3842
4013
|
}
|
|
3843
4014
|
defaultContent(meta) {
|
|
3844
4015
|
const { n, variant, day } = meta;
|
|
@@ -3866,7 +4037,7 @@ var Calendar = class {
|
|
|
3866
4037
|
addHandles(el, meta) {
|
|
3867
4038
|
const { n, variant } = meta;
|
|
3868
4039
|
const ports = variant === "timeline" && !meta.mirror && n.id !== EXTERNAL_ID && !!this.opts.timeline?.onLink;
|
|
3869
|
-
if (variant === "dot" || !this.isEditable(n, "duration")) {
|
|
4040
|
+
if (variant === "dot" || !this.isEditable(n, "duration", true)) {
|
|
3870
4041
|
if (ports) this.addPorts(el, n);
|
|
3871
4042
|
return;
|
|
3872
4043
|
}
|
|
@@ -3890,6 +4061,38 @@ var Calendar = class {
|
|
|
3890
4061
|
}
|
|
3891
4062
|
}
|
|
3892
4063
|
/* ============================================================== popover */
|
|
4064
|
+
onMoreGeometryChange = () => this.positionMore();
|
|
4065
|
+
listenMoreGeometry(on) {
|
|
4066
|
+
const method = on ? "addEventListener" : "removeEventListener";
|
|
4067
|
+
this.doc[method]("scroll", this.onMoreGeometryChange, true);
|
|
4068
|
+
this.win[method]("resize", this.onMoreGeometryChange);
|
|
4069
|
+
this.win.visualViewport?.[method]("resize", this.onMoreGeometryChange);
|
|
4070
|
+
this.win.visualViewport?.[method]("scroll", this.onMoreGeometryChange);
|
|
4071
|
+
}
|
|
4072
|
+
positionMore() {
|
|
4073
|
+
const popover = this.popover;
|
|
4074
|
+
if (!popover) return;
|
|
4075
|
+
if (!popover.anchor.isConnected) {
|
|
4076
|
+
this.closeMore();
|
|
4077
|
+
return;
|
|
4078
|
+
}
|
|
4079
|
+
const viewport = this.win.visualViewport;
|
|
4080
|
+
const root = this.root.getBoundingClientRect();
|
|
4081
|
+
const anchor = popover.anchor.getBoundingClientRect();
|
|
4082
|
+
const leftEdge = Math.max(root.left, viewport?.offsetLeft ?? 0) + 4;
|
|
4083
|
+
const topEdge = Math.max(root.top, viewport?.offsetTop ?? 0) + 4;
|
|
4084
|
+
const rightEdge = Math.min(root.right, (viewport?.offsetLeft ?? 0) + (viewport?.width ?? this.win.innerWidth)) - 4;
|
|
4085
|
+
const bottomEdge = Math.min(root.bottom, (viewport?.offsetTop ?? 0) + (viewport?.height ?? this.win.innerHeight)) - 4;
|
|
4086
|
+
if (rightEdge <= leftEdge || bottomEdge <= topEdge) {
|
|
4087
|
+
this.closeMore();
|
|
4088
|
+
return;
|
|
4089
|
+
}
|
|
4090
|
+
const width = Math.min(360, rightEdge - leftEdge);
|
|
4091
|
+
setStyle(popover.el, { width, "max-height": Math.min(420, bottomEdge - topEdge) });
|
|
4092
|
+
const left = Math.max(leftEdge, Math.min(anchor.left - 12, rightEdge - width));
|
|
4093
|
+
const top = Math.max(topEdge, Math.min(anchor.top - 40, bottomEdge - popover.el.offsetHeight));
|
|
4094
|
+
setStyle(popover.el, { left: left - root.left, top: top - root.top });
|
|
4095
|
+
}
|
|
3893
4096
|
refreshMore() {
|
|
3894
4097
|
const p = this.popover;
|
|
3895
4098
|
if (!p) return;
|
|
@@ -3899,6 +4102,7 @@ var Calendar = class {
|
|
|
3899
4102
|
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 });
|
|
3900
4103
|
}
|
|
3901
4104
|
p.pool.end();
|
|
4105
|
+
this.positionMore();
|
|
3902
4106
|
}
|
|
3903
4107
|
closeMore(restoreFocus = false) {
|
|
3904
4108
|
const p = this.popover;
|
|
@@ -3926,6 +4130,7 @@ var Calendar = class {
|
|
|
3926
4130
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
3927
4131
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
3928
4132
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
4133
|
+
this.listenMoreGeometry(true);
|
|
3929
4134
|
}
|
|
3930
4135
|
unbindEvents() {
|
|
3931
4136
|
const r = this.root;
|
|
@@ -3944,6 +4149,7 @@ var Calendar = class {
|
|
|
3944
4149
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
3945
4150
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
3946
4151
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
4152
|
+
this.listenMoreGeometry(false);
|
|
3947
4153
|
this.listenDoc(false);
|
|
3948
4154
|
}
|
|
3949
4155
|
listenDoc(on) {
|
|
@@ -3959,6 +4165,10 @@ var Calendar = class {
|
|
|
3959
4165
|
return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
|
|
3960
4166
|
}
|
|
3961
4167
|
onDocPointerDown = (ev) => {
|
|
4168
|
+
if (this.cancelledPointerId !== null) {
|
|
4169
|
+
this.cancelledPointerId = null;
|
|
4170
|
+
this.listenDoc(false);
|
|
4171
|
+
}
|
|
3962
4172
|
const t = ev.target;
|
|
3963
4173
|
if (this.popover && !this.popover.el.contains(t) && !this.popover.anchor.contains(t)) this.closeMore();
|
|
3964
4174
|
if (this.selection && !this.root.contains(t)) this.unselect();
|
|
@@ -3966,6 +4176,7 @@ var Calendar = class {
|
|
|
3966
4176
|
onPointerDown = (ev) => {
|
|
3967
4177
|
if (ev.button !== 0 || this.session) return;
|
|
3968
4178
|
const target = ev.target;
|
|
4179
|
+
if (target.closest(".tnc-dg--compact .tnc-dg-day")) return;
|
|
3969
4180
|
if (target.closest("button, a, input, select, textarea, .tnc-toolbar, .tnc-port, [data-tnc-no-drag]")) {
|
|
3970
4181
|
if (!target.closest(".tnc-item")) return;
|
|
3971
4182
|
if (target.closest(".tnc-port, [data-tnc-no-drag], input, select, textarea")) return;
|
|
@@ -3989,6 +4200,7 @@ var Calendar = class {
|
|
|
3989
4200
|
mode = "select";
|
|
3990
4201
|
}
|
|
3991
4202
|
const touch = ev.pointerType === "touch";
|
|
4203
|
+
const overflowOrigin = itemEl?.closest(".tnc-popover") && origin ? wallTime(origin.startMs, this.zone) : null;
|
|
3992
4204
|
const s = {
|
|
3993
4205
|
mode,
|
|
3994
4206
|
pointerId: ev.pointerId,
|
|
@@ -3997,7 +4209,7 @@ var Calendar = class {
|
|
|
3997
4209
|
y0: ev.clientY,
|
|
3998
4210
|
active: false,
|
|
3999
4211
|
origin,
|
|
4000
|
-
grab: this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
4212
|
+
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,
|
|
4001
4213
|
proposal: null,
|
|
4002
4214
|
denied: false,
|
|
4003
4215
|
timer: null,
|
|
@@ -4022,16 +4234,79 @@ var Calendar = class {
|
|
|
4022
4234
|
};
|
|
4023
4235
|
activate(s) {
|
|
4024
4236
|
s.active = true;
|
|
4237
|
+
if (s.lastEvent) this.leaveHover(s.lastEvent);
|
|
4025
4238
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
4026
4239
|
s.timer = null;
|
|
4027
4240
|
if (s.origin) {
|
|
4028
4241
|
this.draggingId = s.origin.id;
|
|
4029
|
-
for (const el of this.elementsFor(s.origin.id))
|
|
4242
|
+
for (const el of this.elementsFor(s.origin.id)) {
|
|
4243
|
+
if (this.opts.view !== "list" && s.mode === "move") {
|
|
4244
|
+
const source = el.cloneNode(true);
|
|
4245
|
+
source.classList.remove("is-selected");
|
|
4246
|
+
source.classList.add("tnc-drag-source");
|
|
4247
|
+
source.setAttribute("aria-hidden", "true");
|
|
4248
|
+
source.inert = true;
|
|
4249
|
+
for (const node of [source, ...source.querySelectorAll("[id], [data-id], [data-task-path]")]) {
|
|
4250
|
+
node.removeAttribute("id");
|
|
4251
|
+
node.removeAttribute("data-id");
|
|
4252
|
+
node.removeAttribute("data-task-path");
|
|
4253
|
+
}
|
|
4254
|
+
el.before(source);
|
|
4255
|
+
this.dragSources.push(source);
|
|
4256
|
+
}
|
|
4257
|
+
if (this.opts.view === "list" && !this.listDragPreview && s.mode === "move") {
|
|
4258
|
+
const bounds = el.getBoundingClientRect();
|
|
4259
|
+
const preview = el.cloneNode(true);
|
|
4260
|
+
preview.classList.add("tnc-drag-preview", "is-dragging");
|
|
4261
|
+
preview.removeAttribute("data-id");
|
|
4262
|
+
preview.removeAttribute("data-task-path");
|
|
4263
|
+
preview.setAttribute("aria-hidden", "true");
|
|
4264
|
+
preview.inert = true;
|
|
4265
|
+
preview.style.width = `${bounds.width}px`;
|
|
4266
|
+
this.root.append(preview);
|
|
4267
|
+
this.listDragPreview = { element: preview, offsetX: s.x0 - bounds.left, offsetY: s.y0 - bounds.top, width: bounds.width, height: bounds.height };
|
|
4268
|
+
}
|
|
4269
|
+
el.classList.add("is-dragging");
|
|
4270
|
+
}
|
|
4030
4271
|
}
|
|
4031
4272
|
this.closeMore();
|
|
4032
4273
|
if (s.mode !== "select") this.unselectSilently();
|
|
4033
4274
|
this.root.classList.add("tnc-dragging", `tnc-dragging-${s.mode}`);
|
|
4034
4275
|
if (s.lastEvent) this.track(s, s.lastEvent);
|
|
4276
|
+
this.startDragScroll();
|
|
4277
|
+
}
|
|
4278
|
+
startDragScroll() {
|
|
4279
|
+
if (this.dragScrollFrame !== null) return;
|
|
4280
|
+
const tick = () => {
|
|
4281
|
+
this.dragScrollFrame = null;
|
|
4282
|
+
const session = this.session;
|
|
4283
|
+
const pointer = session?.lastEvent;
|
|
4284
|
+
if (!session?.active || !pointer) return;
|
|
4285
|
+
const origin = session.origin ? this.elementsFor(session.origin.id)[0] : this.viewHost;
|
|
4286
|
+
const underPointer = this.doc.elementFromPoint(pointer.clientX, pointer.clientY);
|
|
4287
|
+
let moved = false;
|
|
4288
|
+
if (origin && underPointer && this.root.contains(underPointer)) {
|
|
4289
|
+
for (const axis of ["x", "y"]) {
|
|
4290
|
+
const scroller = scrollerOf(origin, axis);
|
|
4291
|
+
if (!scroller) continue;
|
|
4292
|
+
const viewport = scroller === this.doc.scrollingElement ? { left: 0, top: 0, right: this.win.innerWidth, bottom: this.win.innerHeight } : scroller.getBoundingClientRect();
|
|
4293
|
+
const coordinate = axis === "x" ? pointer.clientX : pointer.clientY;
|
|
4294
|
+
const lower = Math.max(0, axis === "x" ? viewport.left : viewport.top);
|
|
4295
|
+
const upper = Math.min(axis === "x" ? this.win.innerWidth : this.win.innerHeight, axis === "x" ? viewport.right : viewport.bottom);
|
|
4296
|
+
if (coordinate < lower || coordinate > upper) continue;
|
|
4297
|
+
const delta = coordinate < lower + 32 ? -Math.min(12, (lower + 32 - coordinate) / 2) : coordinate > upper - 32 ? Math.min(12, (coordinate - upper + 32) / 2) : 0;
|
|
4298
|
+
if (!delta) continue;
|
|
4299
|
+
if (!this.dragScrollOrigins.has(scroller)) this.dragScrollOrigins.set(scroller, { top: scroller.scrollTop, left: scroller.scrollLeft });
|
|
4300
|
+
const before = axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
|
|
4301
|
+
if (axis === "x") scroller.scrollLeft += delta;
|
|
4302
|
+
else scroller.scrollTop += delta;
|
|
4303
|
+
moved ||= before !== (axis === "x" ? scroller.scrollLeft : scroller.scrollTop);
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
if (moved) this.track(session, pointer);
|
|
4307
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4308
|
+
};
|
|
4309
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4035
4310
|
}
|
|
4036
4311
|
onTouchMove = (ev) => {
|
|
4037
4312
|
if (this.session?.active) ev.preventDefault();
|
|
@@ -4061,6 +4336,12 @@ var Calendar = class {
|
|
|
4061
4336
|
this.track(s, ev);
|
|
4062
4337
|
};
|
|
4063
4338
|
onPointerUp = (ev) => {
|
|
4339
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
4340
|
+
this.cancelledPointerId = null;
|
|
4341
|
+
this.suppressNextClick();
|
|
4342
|
+
this.listenDoc(false);
|
|
4343
|
+
return;
|
|
4344
|
+
}
|
|
4064
4345
|
const s = this.session;
|
|
4065
4346
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
4066
4347
|
if (!s.active) {
|
|
@@ -4073,28 +4354,49 @@ var Calendar = class {
|
|
|
4073
4354
|
return;
|
|
4074
4355
|
}
|
|
4075
4356
|
this.suppressNextClick();
|
|
4357
|
+
this.track(s, ev);
|
|
4076
4358
|
this.endSession(ev, false);
|
|
4077
4359
|
};
|
|
4078
4360
|
onPointerCancel = (ev) => {
|
|
4361
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
4362
|
+
this.cancelledPointerId = null;
|
|
4363
|
+
this.listenDoc(false);
|
|
4364
|
+
}
|
|
4079
4365
|
if (this.session && ev.pointerId === this.session.pointerId) this.endSession(null, true);
|
|
4080
4366
|
};
|
|
4081
4367
|
onDocKeyDown = (ev) => {
|
|
4082
4368
|
if (ev.key === "Escape" && this.session) {
|
|
4083
4369
|
ev.preventDefault();
|
|
4084
4370
|
ev.stopPropagation();
|
|
4371
|
+
const pointerId = this.session.active ? this.session.pointerId : null;
|
|
4085
4372
|
this.endSession(null, true);
|
|
4373
|
+
if (pointerId !== null) {
|
|
4374
|
+
this.cancelledPointerId = pointerId;
|
|
4375
|
+
this.listenDoc(true);
|
|
4376
|
+
}
|
|
4086
4377
|
}
|
|
4087
4378
|
};
|
|
4088
4379
|
/** Proposal for the current pointer position. */
|
|
4089
4380
|
track(s, ev) {
|
|
4381
|
+
if (this.listDragPreview) {
|
|
4382
|
+
const preview = this.listDragPreview;
|
|
4383
|
+
const bounds = this.root.getBoundingClientRect();
|
|
4384
|
+
preview.element.style.left = `${Math.max(8, Math.min(this.win.innerWidth - preview.width - 8, ev.clientX - preview.offsetX)) - bounds.left}px`;
|
|
4385
|
+
preview.element.style.top = `${Math.max(8, Math.min(this.win.innerHeight - preview.height - 8, ev.clientY - preview.offsetY)) - bounds.top}px`;
|
|
4386
|
+
}
|
|
4090
4387
|
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4091
4388
|
if (s.mode === "external") {
|
|
4092
4389
|
this.trackExternal(s, hit);
|
|
4093
4390
|
return;
|
|
4094
4391
|
}
|
|
4095
4392
|
if (!s.grab) s.grab = hit;
|
|
4096
|
-
if (!hit || !s.grab)
|
|
4393
|
+
if (!hit || !s.grab) {
|
|
4394
|
+
this.invalidateProposal(s);
|
|
4395
|
+
return;
|
|
4396
|
+
}
|
|
4097
4397
|
if (s.mode === "select") {
|
|
4398
|
+
s.denied = false;
|
|
4399
|
+
this.root.classList.remove("tnc-drop-denied");
|
|
4098
4400
|
const sel = this.selectionFor(s.grab, hit);
|
|
4099
4401
|
if (!sel) return;
|
|
4100
4402
|
this.selection = sel;
|
|
@@ -4103,25 +4405,40 @@ var Calendar = class {
|
|
|
4103
4405
|
}
|
|
4104
4406
|
const origin = s.origin;
|
|
4105
4407
|
const p = s.mode === "move" ? this.moveProposal(origin, s.grab, hit) : this.resizeProposal(origin, hit, s.mode === "resize-start");
|
|
4106
|
-
if (!p)
|
|
4408
|
+
if (!p) {
|
|
4409
|
+
this.invalidateProposal(s);
|
|
4410
|
+
return;
|
|
4411
|
+
}
|
|
4107
4412
|
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;
|
|
4108
4413
|
s.denied = !allowed;
|
|
4109
4414
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
4110
|
-
if (!allowed
|
|
4415
|
+
if (!allowed) {
|
|
4416
|
+
this.invalidateProposal(s);
|
|
4417
|
+
return;
|
|
4418
|
+
}
|
|
4419
|
+
if (sameProposal(p, s.proposal)) return;
|
|
4111
4420
|
s.proposal = p;
|
|
4112
4421
|
this.dragRow = p.rowId !== void 0 ? { id: origin.id, rowId: p.rowId } : null;
|
|
4113
|
-
this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4422
|
+
if (this.opts.view !== "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4423
|
+
}
|
|
4424
|
+
invalidateProposal(s) {
|
|
4425
|
+
const hadProposal = s.proposal !== null;
|
|
4426
|
+
s.proposal = null;
|
|
4427
|
+
s.hit = null;
|
|
4428
|
+
s.denied = true;
|
|
4429
|
+
this.dragRow = null;
|
|
4430
|
+
this.root.classList.add("tnc-drop-denied");
|
|
4431
|
+
this.strip?.markTarget(null);
|
|
4432
|
+
this.unselectSilently();
|
|
4433
|
+
if (s.mode === "external") this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
4434
|
+
else if (hadProposal && s.origin) this.applyChange(this.store.upsert([s.origin.item]));
|
|
4114
4435
|
}
|
|
4115
4436
|
trackExternal(s, raw) {
|
|
4116
4437
|
const src = s.source;
|
|
4117
4438
|
const hit = raw?.kind === "timeline" && raw.area !== "track" && raw.area !== "header" ? null : raw;
|
|
4118
4439
|
s.hit = hit;
|
|
4119
4440
|
if (!hit) {
|
|
4120
|
-
|
|
4121
|
-
s.proposal = null;
|
|
4122
|
-
this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
4123
|
-
}
|
|
4124
|
-
this.unselectSilently();
|
|
4441
|
+
this.invalidateProposal(s);
|
|
4125
4442
|
return;
|
|
4126
4443
|
}
|
|
4127
4444
|
const dur = src.durationMinutes ?? this.opts.defaultTimedMinutes;
|
|
@@ -4140,6 +4457,10 @@ var Calendar = class {
|
|
|
4140
4457
|
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;
|
|
4141
4458
|
s.denied = !allowed;
|
|
4142
4459
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
4460
|
+
if (!allowed) {
|
|
4461
|
+
this.invalidateProposal(s);
|
|
4462
|
+
return;
|
|
4463
|
+
}
|
|
4143
4464
|
if (sameProposal(p, s.proposal)) return;
|
|
4144
4465
|
s.proposal = p;
|
|
4145
4466
|
if (hit.kind === "timeline" && p.rowId === void 0) {
|
|
@@ -4332,7 +4653,18 @@ var Calendar = class {
|
|
|
4332
4653
|
endSession(ev, cancel) {
|
|
4333
4654
|
const s = this.session;
|
|
4334
4655
|
if (!s) return;
|
|
4656
|
+
if (this.dragScrollFrame !== null) this.win.cancelAnimationFrame(this.dragScrollFrame);
|
|
4657
|
+
this.dragScrollFrame = null;
|
|
4658
|
+
if (cancel || s.denied) for (const [scroller, position] of this.dragScrollOrigins) {
|
|
4659
|
+
scroller.scrollTop = position.top;
|
|
4660
|
+
scroller.scrollLeft = position.left;
|
|
4661
|
+
}
|
|
4662
|
+
this.dragScrollOrigins.clear();
|
|
4335
4663
|
this.session = null;
|
|
4664
|
+
this.listDragPreview?.element.remove();
|
|
4665
|
+
this.listDragPreview = null;
|
|
4666
|
+
for (const source of this.dragSources) source.remove();
|
|
4667
|
+
this.dragSources = [];
|
|
4336
4668
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
4337
4669
|
this.listenDoc(false);
|
|
4338
4670
|
this.root.classList.remove(
|
|
@@ -4401,10 +4733,16 @@ var Calendar = class {
|
|
|
4401
4733
|
revert();
|
|
4402
4734
|
return;
|
|
4403
4735
|
}
|
|
4736
|
+
if (this.opts.view === "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4404
4737
|
this.flush();
|
|
4405
4738
|
this.commit(s.mode === "move" ? "drop" : "resize", origin, p, ev, revert);
|
|
4406
4739
|
}
|
|
4407
4740
|
commit(kind, origin, p, ev, revert) {
|
|
4741
|
+
let reverted = false;
|
|
4742
|
+
const mark = (state) => {
|
|
4743
|
+
this.itemWriteStates.set(origin.id, state);
|
|
4744
|
+
for (const element of this.elementsFor(origin.id)) this.applyItemWriteState(element, origin.id);
|
|
4745
|
+
};
|
|
4408
4746
|
const arg = {
|
|
4409
4747
|
item: origin.item,
|
|
4410
4748
|
start: new Date(p.startMs),
|
|
@@ -4420,19 +4758,32 @@ var Calendar = class {
|
|
|
4420
4758
|
jsEvent: ev,
|
|
4421
4759
|
source: ev && "key" in ev ? "keyboard" : "pointer",
|
|
4422
4760
|
view: this.view,
|
|
4423
|
-
revert
|
|
4761
|
+
revert: () => {
|
|
4762
|
+
reverted = true;
|
|
4763
|
+
revert();
|
|
4764
|
+
mark("failed");
|
|
4765
|
+
}
|
|
4424
4766
|
};
|
|
4425
4767
|
const handler = kind === "drop" ? this.opts.onItemDrop : this.opts.onItemResize;
|
|
4426
4768
|
try {
|
|
4769
|
+
mark("pending");
|
|
4427
4770
|
const res = handler?.(arg);
|
|
4428
4771
|
if (res && typeof res.then === "function") {
|
|
4429
|
-
Promise.resolve(res).
|
|
4430
|
-
|
|
4772
|
+
void Promise.resolve(res).then(() => {
|
|
4773
|
+
if (!reverted) mark("changed");
|
|
4774
|
+
}, () => arg.revert());
|
|
4775
|
+
} else if (!reverted) mark("changed");
|
|
4431
4776
|
} catch (err) {
|
|
4432
|
-
revert();
|
|
4777
|
+
arg.revert();
|
|
4433
4778
|
throw err;
|
|
4434
4779
|
}
|
|
4435
4780
|
}
|
|
4781
|
+
applyItemWriteState(element, id) {
|
|
4782
|
+
const state = this.itemWriteStates.get(id);
|
|
4783
|
+
for (const name of ["pending", "failed", "changed"]) element.classList.toggle(`is-write-${name}`, state === name);
|
|
4784
|
+
if (state === "pending") element.setAttribute("aria-busy", "true");
|
|
4785
|
+
else element.removeAttribute("aria-busy");
|
|
4786
|
+
}
|
|
4436
4787
|
/* --------------------------------------------------------- click / keys */
|
|
4437
4788
|
/** Runs in the capture phase so the click ending a drag never reaches content inside the calendar, React portals included. */
|
|
4438
4789
|
onClickCapture = (ev) => {
|
|
@@ -4442,6 +4793,12 @@ var Calendar = class {
|
|
|
4442
4793
|
ev.preventDefault();
|
|
4443
4794
|
};
|
|
4444
4795
|
onClick = (ev) => {
|
|
4796
|
+
const compactDay = ev.target.closest?.(".tnc-dg--compact .tnc-dg-day");
|
|
4797
|
+
if (compactDay && this.root.contains(compactDay) && !ev.target.closest(".tnc-navlink")) {
|
|
4798
|
+
const day = parseDay(compactDay.dataset.date ?? "");
|
|
4799
|
+
if (day !== null) this.openMore(day, compactDay, ev);
|
|
4800
|
+
return;
|
|
4801
|
+
}
|
|
4445
4802
|
const yearDay = ev.target.closest?.(".tnc-ym-day");
|
|
4446
4803
|
if (yearDay && this.root.contains(yearDay)) {
|
|
4447
4804
|
const day = parseDay(yearDay.dataset.date ?? "");
|
|
@@ -4500,6 +4857,25 @@ var Calendar = class {
|
|
|
4500
4857
|
if (el && meta) this.opts.onItemHover?.({ ...this.itemArg(el, meta), jsEvent: ev, entering: false });
|
|
4501
4858
|
}
|
|
4502
4859
|
onKeyDown = (ev) => {
|
|
4860
|
+
if (this.popover && !ev.altKey && !ev.ctrlKey && !ev.metaKey && ["ArrowDown", "ArrowUp", "Home", "End"].includes(ev.key)) {
|
|
4861
|
+
const rows = [...this.popover.el.querySelectorAll(".tnc-item")];
|
|
4862
|
+
const index = rows.indexOf(this.doc.activeElement);
|
|
4863
|
+
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;
|
|
4864
|
+
ev.preventDefault();
|
|
4865
|
+
ev.stopPropagation();
|
|
4866
|
+
rows[next]?.focus();
|
|
4867
|
+
rows[next]?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
4868
|
+
return;
|
|
4869
|
+
}
|
|
4870
|
+
if (ev.key === "Tab" && this.popover) {
|
|
4871
|
+
const controls = [...this.popover.el.querySelectorAll('button, a[href], [tabindex="0"]')];
|
|
4872
|
+
const index = controls.indexOf(this.doc.activeElement);
|
|
4873
|
+
const next = ev.shiftKey ? index <= 0 ? controls.length - 1 : index - 1 : (index + 1) % controls.length;
|
|
4874
|
+
ev.preventDefault();
|
|
4875
|
+
ev.stopPropagation();
|
|
4876
|
+
controls[next]?.focus();
|
|
4877
|
+
return;
|
|
4878
|
+
}
|
|
4503
4879
|
if (ev.key === "Escape" && this.popover) {
|
|
4504
4880
|
ev.preventDefault();
|
|
4505
4881
|
ev.stopPropagation();
|
|
@@ -4511,7 +4887,8 @@ var Calendar = class {
|
|
|
4511
4887
|
const day = parseDay(target.dataset.date ?? "");
|
|
4512
4888
|
if (day !== null) {
|
|
4513
4889
|
ev.preventDefault();
|
|
4514
|
-
|
|
4890
|
+
if (target.closest(".tnc-dg--compact")) this.openMore(day, target, ev);
|
|
4891
|
+
else this.clickSlot({ kind: "day", day }, ev, true);
|
|
4515
4892
|
}
|
|
4516
4893
|
return;
|
|
4517
4894
|
}
|
|
@@ -4599,9 +4976,13 @@ var Calendar = class {
|
|
|
4599
4976
|
const dt = ev.dataTransfer;
|
|
4600
4977
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
4601
4978
|
ev.preventDefault();
|
|
4979
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4980
|
+
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") {
|
|
4981
|
+
dt.dropEffect = "none";
|
|
4982
|
+
this.unselectSilently();
|
|
4983
|
+
return;
|
|
4984
|
+
}
|
|
4602
4985
|
dt.dropEffect = "copy";
|
|
4603
|
-
const hit = this.current.hit(ev.clientX, ev.clientY);
|
|
4604
|
-
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
4605
4986
|
const sel = this.selectionFor(hit, hit);
|
|
4606
4987
|
if (!sel) return;
|
|
4607
4988
|
this.selection = sel;
|
|
@@ -4615,7 +4996,7 @@ var Calendar = class {
|
|
|
4615
4996
|
const dt = ev.dataTransfer;
|
|
4616
4997
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
4617
4998
|
ev.preventDefault();
|
|
4618
|
-
const hit = this.
|
|
4999
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4619
5000
|
this.unselectSilently();
|
|
4620
5001
|
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
4621
5002
|
const sel = this.selectionFor(hit, hit);
|
|
@@ -4646,7 +5027,7 @@ function scrollerOf(el, axis) {
|
|
|
4646
5027
|
return doc.scrollingElement;
|
|
4647
5028
|
}
|
|
4648
5029
|
function rangeMoved(prev, next) {
|
|
4649
|
-
return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.endDay !== next.dataRange.endDay;
|
|
5030
|
+
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;
|
|
4650
5031
|
}
|
|
4651
5032
|
|
|
4652
5033
|
// src/tasknotes/bases.ts
|