@tasknotes/calendar 0.1.7 → 0.1.10
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 +32 -1
- package/README.md +7 -0
- package/dist/calendar.css +367 -44
- package/dist/cjs/index.cjs +478 -79
- package/dist/cjs/react.cjs +474 -79
- package/dist/esm/{chunk-EDRCVOY7.js → chunk-5PHSJK36.js} +474 -79
- package/dist/esm/index.js +5 -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 +18 -1
- package/dist/types/dom/calendar.d.ts +18 -1
- package/dist/types/dom/format.d.cts +2 -1
- package/dist/types/dom/format.d.ts +2 -1
- package/dist/types/dom/options.d.cts +8 -2
- package/dist/types/dom/options.d.ts +8 -2
- package/dist/types/dom/views/daygrid.d.cts +3 -1
- package/dist/types/dom/views/daygrid.d.ts +3 -1
- package/dist/types/dom/views/list.d.cts +1 -0
- package/dist/types/dom/views/list.d.ts +1 -0
- package/dist/types/dom/views/strip.d.cts +3 -1
- package/dist/types/dom/views/strip.d.ts +3 -1
- package/dist/types/dom/views/timegrid.d.cts +4 -1
- package/dist/types/dom/views/timegrid.d.ts +4 -1
- package/dist/types/dom/views/year.d.cts +1 -0
- package/dist/types/dom/views/year.d.ts +1 -0
- package/dist/types/tasknotes/bases.d.cts +2 -0
- package/dist/types/tasknotes/bases.d.ts +2 -0
- package/package.json +1 -1
package/dist/cjs/react.cjs
CHANGED
|
@@ -575,14 +575,16 @@ function pct(n) {
|
|
|
575
575
|
|
|
576
576
|
// src/dom/format.ts
|
|
577
577
|
var Formatter = class {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
hour12;
|
|
581
|
-
constructor(locale, hour12) {
|
|
578
|
+
constructor(locale, hour12, formatTime) {
|
|
579
|
+
this.formatTime = formatTime;
|
|
582
580
|
this.locale = locale;
|
|
583
581
|
const probe = new Intl.DateTimeFormat(locale, { hour: "numeric" }).resolvedOptions();
|
|
584
582
|
this.hour12 = hour12 ?? (probe.hour12 === true || probe.hourCycle === "h12" || probe.hourCycle === "h11");
|
|
585
583
|
}
|
|
584
|
+
formatTime;
|
|
585
|
+
cache = /* @__PURE__ */ new Map();
|
|
586
|
+
locale;
|
|
587
|
+
hour12;
|
|
586
588
|
fmt(key, opts) {
|
|
587
589
|
let f = this.cache.get(key);
|
|
588
590
|
if (!f) {
|
|
@@ -640,6 +642,7 @@ var Formatter = class {
|
|
|
640
642
|
/** Wall-clock minutes to a label. `compact` drops ":00" in 12-hour formats ("9am"). */
|
|
641
643
|
time(minute, compact = false) {
|
|
642
644
|
const m = (Math.round(minute) % 1440 + 1440) % 1440;
|
|
645
|
+
if (this.formatTime) return this.formatTime(m);
|
|
643
646
|
const date = new Date(Date.UTC(2e3, 0, 1, Math.floor(m / 60), m % 60));
|
|
644
647
|
if (this.hour12) {
|
|
645
648
|
const parts = this.fmt("t12", { hour: "numeric", minute: "2-digit", hour12: true }).formatToParts(date);
|
|
@@ -731,6 +734,7 @@ function defaultOptions() {
|
|
|
731
734
|
snapDuration: void 0,
|
|
732
735
|
scrollTime: "08:00",
|
|
733
736
|
slotHeight: 24,
|
|
737
|
+
pinchZoom: true,
|
|
734
738
|
expandRows: false,
|
|
735
739
|
nowIndicator: true,
|
|
736
740
|
selectMirror: true,
|
|
@@ -772,6 +776,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
772
776
|
"locale",
|
|
773
777
|
"firstDay",
|
|
774
778
|
"hour12",
|
|
779
|
+
"formatTime",
|
|
775
780
|
"weekNumbers",
|
|
776
781
|
"weekends",
|
|
777
782
|
"hiddenDays",
|
|
@@ -784,9 +789,11 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
784
789
|
"slotDuration",
|
|
785
790
|
"snapDuration",
|
|
786
791
|
"slotHeight",
|
|
792
|
+
"pinchZoom",
|
|
787
793
|
"expandRows",
|
|
788
794
|
"nowIndicator",
|
|
789
795
|
"slotEventOverlap",
|
|
796
|
+
"overlapStackThreshold",
|
|
790
797
|
"eventMinHeight",
|
|
791
798
|
"eventMaxStack",
|
|
792
799
|
"dayMaxEvents",
|
|
@@ -982,8 +989,9 @@ var DayGridView = class {
|
|
|
982
989
|
constructor(ctx) {
|
|
983
990
|
this.ctx = ctx;
|
|
984
991
|
const { doc, opts, days } = ctx;
|
|
985
|
-
this.dotsMode = opts.monthDisplay === "dots";
|
|
992
|
+
this.dotsMode = opts.monthDisplay === "dots" || opts.monthDisplay === "chips";
|
|
986
993
|
this.el = h(doc, "div", this.dotsMode ? "tnc-dg tnc-dg--dots" : "tnc-dg");
|
|
994
|
+
this.el.classList.toggle("tnc-dg--chips", opts.monthDisplay === "chips");
|
|
987
995
|
this.scroller = h(doc, "div", "tnc-dg-scroll");
|
|
988
996
|
this.grid = h(doc, "div", "tnc-dg-grid", { role: "grid", "aria-label": ctx.view.title });
|
|
989
997
|
const perWeek = this.daysPerWeek();
|
|
@@ -1013,14 +1021,14 @@ var DayGridView = class {
|
|
|
1013
1021
|
const cell = h(doc, "div", cls.join(" "), { role: "gridcell", "aria-label": ctx.fmt.longDate(d), "data-date": arg.dateStr });
|
|
1014
1022
|
if (this.dotsMode) cell.tabIndex = 0;
|
|
1015
1023
|
const num = ctx.fmt.dayNumber(d);
|
|
1016
|
-
const label = ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num}` : num;
|
|
1024
|
+
const label = !this.dotsMode && ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num}` : num;
|
|
1017
1025
|
cell.append(
|
|
1018
1026
|
h(
|
|
1019
1027
|
doc,
|
|
1020
1028
|
"div",
|
|
1021
1029
|
"tnc-dg-top",
|
|
1022
1030
|
null,
|
|
1023
|
-
opts.navLinks ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
1031
|
+
opts.navLinks && !this.dotsMode ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
1024
1032
|
)
|
|
1025
1033
|
);
|
|
1026
1034
|
dates.append(cell);
|
|
@@ -1034,20 +1042,20 @@ var DayGridView = class {
|
|
|
1034
1042
|
layer.append(content);
|
|
1035
1043
|
row.append(dates, layer);
|
|
1036
1044
|
this.grid.append(row);
|
|
1037
|
-
const dots =
|
|
1045
|
+
const dots = cells.map((cell) => {
|
|
1038
1046
|
const box = h(doc, "div", "tnc-dg-dots", { "aria-hidden": "true" });
|
|
1039
1047
|
if (opts.dayMaxEvents === false) box.classList.add("is-unlimited");
|
|
1040
1048
|
const count = h(doc, "span", "tnc-dg-dots-more");
|
|
1041
1049
|
cell.append(box);
|
|
1042
1050
|
return { box, pool: new ItemPool(ctx, box), count };
|
|
1043
|
-
})
|
|
1051
|
+
});
|
|
1044
1052
|
this.weeks.push({ days: wdays, row, cells, layer, sel, more, pool: new ItemPool(ctx, content), dots });
|
|
1045
1053
|
}
|
|
1046
1054
|
this.scroller.append(this.grid);
|
|
1047
1055
|
this.el.append(this.scroller);
|
|
1048
1056
|
this.el.classList.toggle("tnc-dg--auto", opts.height === "auto");
|
|
1049
1057
|
const RO = ctx.win.ResizeObserver;
|
|
1050
|
-
if (RO
|
|
1058
|
+
if (RO) {
|
|
1051
1059
|
this.resizeObs = new RO(() => {
|
|
1052
1060
|
if (this.sizeKey() !== this.lastHeights) this.render(null);
|
|
1053
1061
|
});
|
|
@@ -1065,6 +1073,7 @@ var DayGridView = class {
|
|
|
1065
1073
|
topPx = 24;
|
|
1066
1074
|
lastHeights = "";
|
|
1067
1075
|
dotsMode;
|
|
1076
|
+
dragPreviews = [];
|
|
1068
1077
|
daysPerWeek() {
|
|
1069
1078
|
const { days } = this.ctx;
|
|
1070
1079
|
const first = days[0];
|
|
@@ -1078,16 +1087,71 @@ var DayGridView = class {
|
|
|
1078
1087
|
}
|
|
1079
1088
|
render(_change) {
|
|
1080
1089
|
const { ctx } = this;
|
|
1090
|
+
this.clearDragPreviews();
|
|
1091
|
+
const dragged = ctx.draggingId ? ctx.store.get(ctx.draggingId) : void 0;
|
|
1092
|
+
const source = dragged ? this.elementsFor(dragged.id)[0] : void 0;
|
|
1093
|
+
if (dragged && source && this.el.closest(".tnc-dragging-move")) {
|
|
1094
|
+
const proposals = [dragged, ...[...ctx.store.all()].filter((item) => item.id !== dragged.id && item.item.preview)];
|
|
1095
|
+
for (const proposal of proposals) {
|
|
1096
|
+
const original = this.elementsFor(proposal.id)[0] ?? source;
|
|
1097
|
+
const bounds = original.getBoundingClientRect();
|
|
1098
|
+
const sourceWeek = original.closest(".tnc-dg-week");
|
|
1099
|
+
const offset = bounds.top - sourceWeek.getBoundingClientRect().top;
|
|
1100
|
+
for (const week of this.weeks) {
|
|
1101
|
+
const start = week.days.findIndex((day) => day >= proposal.startDay && day < proposal.endDay);
|
|
1102
|
+
if (start < 0) continue;
|
|
1103
|
+
let end = start + 1;
|
|
1104
|
+
while (!this.dotsMode && end < week.days.length && week.days[end] < proposal.endDay) end++;
|
|
1105
|
+
const preview = ctx.createItemEl({ n: proposal, day: week.days[start], isStart: proposal.startDay >= week.days[start], isEnd: proposal.endDay <= week.days[end - 1] + 1, variant: ctx.meta.get(original).variant, mirror: true });
|
|
1106
|
+
preview.classList.add("tnc-month-preview");
|
|
1107
|
+
preview.inert = true;
|
|
1108
|
+
setStyle(preview, {
|
|
1109
|
+
left: `calc(${pct(start / week.days.length)} + 2px)`,
|
|
1110
|
+
width: `calc(${pct((end - start) / week.days.length)} - 4px)`,
|
|
1111
|
+
top: `${Math.max(this.topPx, Math.min(offset, week.row.clientHeight - bounds.height - 4))}px`,
|
|
1112
|
+
height: `${bounds.height}px`
|
|
1113
|
+
});
|
|
1114
|
+
week.layer.append(preview);
|
|
1115
|
+
this.dragPreviews.push(preview);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
this.dotsMode = ctx.opts.monthDisplay !== "full" || this.grid.clientWidth / this.daysPerWeek() < 72;
|
|
1121
|
+
this.el.classList.toggle("tnc-dg--dots", this.dotsMode);
|
|
1122
|
+
this.el.classList.toggle("tnc-dg--compact", this.dotsMode && ctx.opts.monthDisplay === "full");
|
|
1123
|
+
this.el.classList.toggle("tnc-dg--chips", ctx.opts.monthDisplay === "chips" && this.weeks[0].dots[0].box.clientHeight >= 44);
|
|
1081
1124
|
const cs = ctx.win.getComputedStyle(this.el);
|
|
1082
1125
|
this.barPx = px(cs, "--tnc-bar-h", 20);
|
|
1083
1126
|
this.topPx = this.weeks[0]?.cells[0]?.querySelector(".tnc-dg-top")?.offsetHeight || px(cs, "--tnc-dg-top-h", 24);
|
|
1084
1127
|
this.morePx = this.measureMore(cs);
|
|
1085
1128
|
this.lastHeights = this.sizeKey();
|
|
1086
1129
|
for (const w of this.weeks) {
|
|
1087
|
-
|
|
1088
|
-
|
|
1130
|
+
for (const cell of w.cells) {
|
|
1131
|
+
if (this.dotsMode) cell.tabIndex = 0;
|
|
1132
|
+
else cell.removeAttribute("tabindex");
|
|
1133
|
+
if (this.dotsMode && ctx.opts.monthDisplay === "full") cell.setAttribute("aria-haspopup", "dialog");
|
|
1134
|
+
else cell.removeAttribute("aria-haspopup");
|
|
1135
|
+
}
|
|
1136
|
+
for (const cell of w.dots) cell.box.hidden = !this.dotsMode;
|
|
1137
|
+
if (this.dotsMode) {
|
|
1138
|
+
w.pool.begin();
|
|
1139
|
+
w.pool.end();
|
|
1140
|
+
w.more.replaceChildren();
|
|
1141
|
+
this.renderDots(w);
|
|
1142
|
+
} else {
|
|
1143
|
+
for (const cell of w.dots) {
|
|
1144
|
+
cell.pool.begin();
|
|
1145
|
+
cell.pool.end();
|
|
1146
|
+
}
|
|
1147
|
+
this.renderWeek(w);
|
|
1148
|
+
}
|
|
1089
1149
|
}
|
|
1090
1150
|
}
|
|
1151
|
+
clearDragPreviews() {
|
|
1152
|
+
for (const preview of this.dragPreviews) this.ctx.destroyItemEl(preview);
|
|
1153
|
+
this.dragPreviews = [];
|
|
1154
|
+
}
|
|
1091
1155
|
/** Height of a "+N more" link as the host styles it (`--tnc-more-h`, or a taller min-height). */
|
|
1092
1156
|
measureMore(cs) {
|
|
1093
1157
|
const fallback = px(cs, "--tnc-more-h", this.barPx);
|
|
@@ -1184,7 +1248,8 @@ var DayGridView = class {
|
|
|
1184
1248
|
w.more.append(link);
|
|
1185
1249
|
}
|
|
1186
1250
|
existing.delete(key);
|
|
1187
|
-
link.
|
|
1251
|
+
link.setAttribute("aria-label", ctx.text.more(items.length));
|
|
1252
|
+
link.textContent = w.row.clientWidth / n < 72 ? `+${new Intl.NumberFormat(ctx.opts.locale).format(items.length)}` : ctx.text.more(items.length);
|
|
1188
1253
|
setStyle(link, {
|
|
1189
1254
|
left: `calc(${pct(col / n)} + 2px)`,
|
|
1190
1255
|
width: `calc(${pct(1 / n)} - 4px)`,
|
|
@@ -1214,10 +1279,11 @@ var DayGridView = class {
|
|
|
1214
1279
|
const { dayMaxEvents } = ctx.opts;
|
|
1215
1280
|
const first = w.dots[0].box;
|
|
1216
1281
|
const cs = ctx.win.getComputedStyle(first);
|
|
1217
|
-
const
|
|
1282
|
+
const chips = this.el.classList.contains("tnc-dg--chips");
|
|
1283
|
+
const size = chips ? px(cs, "--tnc-chip-h", 14) : px(cs, "--tnc-dot-size", 8);
|
|
1218
1284
|
const gap = px(cs, "--tnc-dot-gap", 3);
|
|
1219
1285
|
const step = size + gap;
|
|
1220
|
-
const perRow = Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
1286
|
+
const perRow = chips ? 1 : Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
1221
1287
|
const rows = Math.max(1, Math.floor((first.clientHeight + gap) / step));
|
|
1222
1288
|
const fits = dayMaxEvents === false ? Infinity : perRow * rows;
|
|
1223
1289
|
const cap = typeof dayMaxEvents === "number" && dayMaxEvents > 0 ? Math.min(dayMaxEvents, fits) : fits;
|
|
@@ -1226,7 +1292,7 @@ var DayGridView = class {
|
|
|
1226
1292
|
w.days.forEach((day, i) => {
|
|
1227
1293
|
const cell = w.dots[i];
|
|
1228
1294
|
const items = this.itemsOn(day);
|
|
1229
|
-
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(
|
|
1295
|
+
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(1, cap - (chips ? 1 : 2)) : Math.max(1, cap);
|
|
1230
1296
|
cell.pool.begin();
|
|
1231
1297
|
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 }));
|
|
1232
1298
|
cell.pool.end();
|
|
@@ -1277,6 +1343,7 @@ var DayGridView = class {
|
|
|
1277
1343
|
return this.weeks.flatMap((w) => [...w.pool.elementsFor(id), ...(w.dots ?? []).flatMap((c) => c.pool.elementsFor(id))]);
|
|
1278
1344
|
}
|
|
1279
1345
|
destroy() {
|
|
1346
|
+
this.clearDragPreviews();
|
|
1280
1347
|
this.resizeObs?.disconnect();
|
|
1281
1348
|
for (const w of this.weeks) {
|
|
1282
1349
|
w.pool.clear();
|
|
@@ -1329,6 +1396,7 @@ var ListView = class {
|
|
|
1329
1396
|
frame = null;
|
|
1330
1397
|
/** A picked day stays marked until the list is scrolled by hand. */
|
|
1331
1398
|
pinned = false;
|
|
1399
|
+
pinnedTop = 0;
|
|
1332
1400
|
itemsOn(day) {
|
|
1333
1401
|
const { store, opts } = this.ctx;
|
|
1334
1402
|
const out = store.spanningIn(day, day + 1, true);
|
|
@@ -1343,6 +1411,7 @@ var ListView = class {
|
|
|
1343
1411
|
const leadItems = [];
|
|
1344
1412
|
const inLead = /* @__PURE__ */ new Set();
|
|
1345
1413
|
const keep = /* @__PURE__ */ new Set();
|
|
1414
|
+
let itemCount = 0;
|
|
1346
1415
|
let prev = null;
|
|
1347
1416
|
for (const day of days) {
|
|
1348
1417
|
let items = this.itemsOn(day);
|
|
@@ -1356,7 +1425,7 @@ var ListView = class {
|
|
|
1356
1425
|
return false;
|
|
1357
1426
|
});
|
|
1358
1427
|
}
|
|
1359
|
-
if (!items.length && !agenda) continue;
|
|
1428
|
+
if (!items.length && !agenda && opts.listDayCount !== 1) continue;
|
|
1360
1429
|
keep.add(day);
|
|
1361
1430
|
let section = this.sections.get(day);
|
|
1362
1431
|
let pool = this.pools.get(day);
|
|
@@ -1375,6 +1444,7 @@ var ListView = class {
|
|
|
1375
1444
|
this.pools.set(day, pool);
|
|
1376
1445
|
}
|
|
1377
1446
|
section.classList.toggle("is-empty", !items.length);
|
|
1447
|
+
itemCount += items.length;
|
|
1378
1448
|
this.fill(pool, items, () => day);
|
|
1379
1449
|
if (prev ? prev.nextSibling !== section : this.body.firstChild !== section) {
|
|
1380
1450
|
if (prev) prev.after(section);
|
|
@@ -1390,13 +1460,14 @@ var ListView = class {
|
|
|
1390
1460
|
this.sections.delete(day);
|
|
1391
1461
|
}
|
|
1392
1462
|
this.renderLead(leadItems);
|
|
1393
|
-
let empty = this.body.querySelector("
|
|
1394
|
-
if (!
|
|
1463
|
+
let empty = this.body.querySelector(".tnc-list-empty");
|
|
1464
|
+
if (!itemCount && !leadItems.length && (!agenda || opts.listEmptyContent)) {
|
|
1395
1465
|
if (!empty) {
|
|
1396
1466
|
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
1397
1467
|
const content = ctx.opts.listEmptyContent?.();
|
|
1398
1468
|
if (content) empty.replaceChildren(content);
|
|
1399
|
-
this.
|
|
1469
|
+
if (this.sections.size === 1) this.sections.values().next().value.append(empty);
|
|
1470
|
+
else this.body.prepend(empty);
|
|
1400
1471
|
}
|
|
1401
1472
|
} else empty?.remove();
|
|
1402
1473
|
this.ctx.strip?.draw();
|
|
@@ -1454,9 +1525,10 @@ var ListView = class {
|
|
|
1454
1525
|
const section = day === this.ctx.days[0] && this.lead ? this.lead.section : this.sections.get(day);
|
|
1455
1526
|
if (!section) return;
|
|
1456
1527
|
this.ctx.strip?.measure();
|
|
1457
|
-
section.
|
|
1528
|
+
this.scroller.scrollTop += section.getBoundingClientRect().top - this.scroller.getBoundingClientRect().top;
|
|
1458
1529
|
this.ctx.strip?.setFocus(day);
|
|
1459
1530
|
this.pinned = true;
|
|
1531
|
+
this.pinnedTop = this.scroller.scrollTop;
|
|
1460
1532
|
}
|
|
1461
1533
|
fold = () => {
|
|
1462
1534
|
this.pinned = false;
|
|
@@ -1464,6 +1536,7 @@ var ListView = class {
|
|
|
1464
1536
|
if (strip?.expanded) strip.setExpanded(false);
|
|
1465
1537
|
};
|
|
1466
1538
|
onScroll = () => {
|
|
1539
|
+
if (this.pinned && Math.abs(this.scroller.scrollTop - this.pinnedTop) > 1) this.pinned = false;
|
|
1467
1540
|
if (this.frame !== null) return;
|
|
1468
1541
|
this.frame = this.ctx.win.requestAnimationFrame(() => {
|
|
1469
1542
|
this.frame = null;
|
|
@@ -1474,7 +1547,7 @@ var ListView = class {
|
|
|
1474
1547
|
followTop() {
|
|
1475
1548
|
const strip = this.ctx.strip;
|
|
1476
1549
|
if (!strip || this.pinned) return;
|
|
1477
|
-
const edge = strip.el.getBoundingClientRect().bottom + 1;
|
|
1550
|
+
const edge = Math.max(strip.el.getBoundingClientRect().bottom, this.scroller.getBoundingClientRect().top) + 1;
|
|
1478
1551
|
for (const day of this.ctx.days) {
|
|
1479
1552
|
const section = this.sections.get(day);
|
|
1480
1553
|
if (section && section.getBoundingClientRect().bottom > edge) {
|
|
@@ -1522,10 +1595,11 @@ var ListView = class {
|
|
|
1522
1595
|
// src/dom/views/strip.ts
|
|
1523
1596
|
var SWIPE_PX = 40;
|
|
1524
1597
|
var DateStrip = class {
|
|
1525
|
-
constructor(ctx, pick, focus, onShown) {
|
|
1598
|
+
constructor(ctx, pick, focus, onShown, onPage) {
|
|
1526
1599
|
this.ctx = ctx;
|
|
1527
1600
|
this.pick = pick;
|
|
1528
1601
|
this.onShown = onShown;
|
|
1602
|
+
this.onPage = onPage;
|
|
1529
1603
|
const { doc, text } = ctx;
|
|
1530
1604
|
this.focus = focus;
|
|
1531
1605
|
this.shown = startOfWeek(focus, ctx.firstDay);
|
|
@@ -1543,13 +1617,16 @@ var DateStrip = class {
|
|
|
1543
1617
|
head.append(this.title, this.todayBtn, prev, next, this.slot);
|
|
1544
1618
|
this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
|
|
1545
1619
|
this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
|
|
1546
|
-
const handle = h(doc, "
|
|
1547
|
-
handle.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
1620
|
+
const handle = h(doc, "div", "tnc-strip-handle", { "aria-hidden": "true" });
|
|
1548
1621
|
this.el.append(head, this.dows, this.grid, handle);
|
|
1549
1622
|
this.grid.addEventListener("click", this.onClick);
|
|
1550
1623
|
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
1551
1624
|
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
1552
1625
|
this.el.addEventListener("pointerup", this.onPointerUp);
|
|
1626
|
+
this.el.addEventListener("touchstart", this.ownTouch, { passive: true });
|
|
1627
|
+
this.el.addEventListener("touchmove", this.ownTouch, { passive: true });
|
|
1628
|
+
this.el.addEventListener("touchend", this.ownTouch, { passive: true });
|
|
1629
|
+
this.el.addEventListener("touchcancel", this.ownTouch, { passive: true });
|
|
1553
1630
|
this.el.addEventListener("pointercancel", this.onPointerCancel);
|
|
1554
1631
|
this.el.addEventListener("click", this.onClickCapture, true);
|
|
1555
1632
|
this.draw();
|
|
@@ -1558,6 +1635,7 @@ var DateStrip = class {
|
|
|
1558
1635
|
ctx;
|
|
1559
1636
|
pick;
|
|
1560
1637
|
onShown;
|
|
1638
|
+
onPage;
|
|
1561
1639
|
el;
|
|
1562
1640
|
title;
|
|
1563
1641
|
dows;
|
|
@@ -1616,6 +1694,7 @@ var DateStrip = class {
|
|
|
1616
1694
|
step(dir) {
|
|
1617
1695
|
this.shown = this.expanded ? addMonths(this.shown, dir) : this.shown + 7 * dir;
|
|
1618
1696
|
this.draw();
|
|
1697
|
+
this.onPage?.(this.expanded ? this.shown : this.focus + 7 * dir);
|
|
1619
1698
|
}
|
|
1620
1699
|
days() {
|
|
1621
1700
|
if (!this.expanded) return Array.from({ length: 7 }, (_, i) => this.shown + i);
|
|
@@ -1640,7 +1719,8 @@ var DateStrip = class {
|
|
|
1640
1719
|
const tabDay = days.includes(this.focus) ? this.focus : days[0];
|
|
1641
1720
|
const cells = days.map((d) => {
|
|
1642
1721
|
const arg = ctx.dayArg(d);
|
|
1643
|
-
const
|
|
1722
|
+
const items = store.inRange(d, d + 1).filter((n) => n.item.display !== "background" && n.item.display !== "none");
|
|
1723
|
+
const count = items.length;
|
|
1644
1724
|
const first = ymdOfDay(d).day === 1 && d !== days[0] && !this.expanded;
|
|
1645
1725
|
const label = fmt.longDate(d);
|
|
1646
1726
|
const cell = h(
|
|
@@ -1664,7 +1744,15 @@ var DateStrip = class {
|
|
|
1664
1744
|
}
|
|
1665
1745
|
);
|
|
1666
1746
|
if (first) cell.append(h(doc, "span", "tnc-strip-month", { "aria-hidden": "true" }, fmt.monthShort(d)));
|
|
1667
|
-
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d))
|
|
1747
|
+
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d)));
|
|
1748
|
+
const dots = h(doc, "span", "tnc-strip-dots", { "aria-hidden": "true" });
|
|
1749
|
+
for (const item of items.slice(0, 3)) {
|
|
1750
|
+
const dot = h(doc, "span", "tnc-strip-dot");
|
|
1751
|
+
const color = item.item.color || item.item.borderColor || item.item.backgroundColor;
|
|
1752
|
+
if (color) dot.style.backgroundColor = color;
|
|
1753
|
+
dots.append(dot);
|
|
1754
|
+
}
|
|
1755
|
+
cell.append(dots);
|
|
1668
1756
|
return cell;
|
|
1669
1757
|
});
|
|
1670
1758
|
this.grid.replaceChildren(...cells);
|
|
@@ -1704,6 +1792,9 @@ var DateStrip = class {
|
|
|
1704
1792
|
if (ev.button !== 0 || this.ctx.draggingId) return;
|
|
1705
1793
|
this.swipe = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
|
|
1706
1794
|
};
|
|
1795
|
+
ownTouch = (event) => {
|
|
1796
|
+
event.stopPropagation();
|
|
1797
|
+
};
|
|
1707
1798
|
onPointerUp = (ev) => {
|
|
1708
1799
|
const s = this.swipe;
|
|
1709
1800
|
this.swipe = null;
|
|
@@ -1743,6 +1834,7 @@ var TimeGridView = class {
|
|
|
1743
1834
|
const showGrid = opts.timeGrid;
|
|
1744
1835
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1745
1836
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1837
|
+
this.el.classList.toggle("tnc-tg--pinch", showGrid && opts.pinchZoom);
|
|
1746
1838
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1747
1839
|
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1748
1840
|
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
@@ -1892,6 +1984,11 @@ var TimeGridView = class {
|
|
|
1892
1984
|
}
|
|
1893
1985
|
if (this.nowGutter) gutter.append(this.nowGutter);
|
|
1894
1986
|
}
|
|
1987
|
+
/** Slot height as drawn, which `expandRows` can stretch past the option. */
|
|
1988
|
+
get drawnSlotHeight() {
|
|
1989
|
+
return this.ctx.slot.duration * this.ppm;
|
|
1990
|
+
}
|
|
1991
|
+
/** Applies `slotHeight`. Items are placed in percentages, so they follow without a render. */
|
|
1895
1992
|
applyScale() {
|
|
1896
1993
|
const { opts, slot } = this.ctx;
|
|
1897
1994
|
this.ppm = opts.slotHeight / slot.duration;
|
|
@@ -2072,15 +2169,17 @@ var TimeGridView = class {
|
|
|
2072
2169
|
const { opts } = ctx;
|
|
2073
2170
|
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
2074
2171
|
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
2075
|
-
const
|
|
2172
|
+
const overlapThreshold = opts.overlapStackThreshold ?? 0;
|
|
2173
|
+
const collapsedClusters = new Set(placements.filter((placement) => {
|
|
2076
2174
|
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2175
|
+
const denseOverlap = overlapThreshold > 1 && placement.cols >= overlapThreshold;
|
|
2176
|
+
return columns > 1 && (columns * readableWidth > laneWidth || denseOverlap);
|
|
2177
|
+
}).map((placement) => placement.cluster));
|
|
2081
2178
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
2082
2179
|
pool.begin();
|
|
2083
2180
|
for (const p of placements) {
|
|
2181
|
+
const withMore = lane === "plan" || collapsedClusters.has(p.cluster);
|
|
2182
|
+
const maxStack = collapsedClusters.has(p.cluster) ? 1 : opts.eventMaxStack;
|
|
2084
2183
|
const preview = p.item.item.preview || p.item.id === ctx.draggingId;
|
|
2085
2184
|
if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
|
|
2086
2185
|
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
|
|
@@ -2093,7 +2192,7 @@ var TimeGridView = class {
|
|
|
2093
2192
|
}
|
|
2094
2193
|
const cols = !preview && withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
|
|
2095
2194
|
const span = Math.min(p.span, cols - p.col);
|
|
2096
|
-
const left = p.col / cols;
|
|
2195
|
+
const left = p.item.id === ctx.draggingId ? 0 : p.col / cols;
|
|
2097
2196
|
let width = span / cols;
|
|
2098
2197
|
if (opts.slotEventOverlap && cols > 1 && p.col + span < cols) width = Math.min(1 - left, width * 1.7);
|
|
2099
2198
|
const el = pool.place(p.item.id, this.meta(p, false));
|
|
@@ -2108,8 +2207,9 @@ var TimeGridView = class {
|
|
|
2108
2207
|
});
|
|
2109
2208
|
}
|
|
2110
2209
|
pool.end();
|
|
2111
|
-
if (!withMore) return;
|
|
2112
2210
|
for (const [cluster, rec] of hiddenByCluster) {
|
|
2211
|
+
const collapse = collapsedClusters.has(cluster);
|
|
2212
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2113
2213
|
const key = `${lane}-${cluster}`;
|
|
2114
2214
|
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
2115
2215
|
if (!link) {
|
|
@@ -2123,8 +2223,15 @@ var TimeGridView = class {
|
|
|
2123
2223
|
link.classList.toggle("tnc-more--collision", collapse);
|
|
2124
2224
|
link.textContent = `+${rec.count}`;
|
|
2125
2225
|
if (collapse) {
|
|
2126
|
-
|
|
2127
|
-
|
|
2226
|
+
const items = placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.item.title);
|
|
2227
|
+
const count = ctx.text.appointments(rec.count);
|
|
2228
|
+
const interval = `${ctx.fmt.time(rec.start)} \u2013 ${ctx.fmt.time(rec.end)}`;
|
|
2229
|
+
link.replaceChildren(
|
|
2230
|
+
h(ctx.doc, "span", "tnc-collision-count", null, String(rec.count)),
|
|
2231
|
+
h(ctx.doc, "span", "tnc-collision-range", null, interval),
|
|
2232
|
+
h(ctx.doc, "span", "tnc-collision-title", { "aria-hidden": "true" }, items.join(" \xB7 "))
|
|
2233
|
+
);
|
|
2234
|
+
link.setAttribute("aria-label", `${count}, ${interval}: ${items.join(", ")}`);
|
|
2128
2235
|
link.title = link.getAttribute("aria-label");
|
|
2129
2236
|
} else {
|
|
2130
2237
|
link.removeAttribute("aria-label");
|
|
@@ -2133,6 +2240,7 @@ var TimeGridView = class {
|
|
|
2133
2240
|
const cols = maxStack;
|
|
2134
2241
|
setStyle(link, {
|
|
2135
2242
|
top: top(rec.start),
|
|
2243
|
+
height: collapse ? pct((rec.end - rec.start) / this.minutes) : "",
|
|
2136
2244
|
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
2137
2245
|
});
|
|
2138
2246
|
}
|
|
@@ -2199,12 +2307,10 @@ var TimeGridView = class {
|
|
|
2199
2307
|
const r2 = cells[i2].getBoundingClientRect();
|
|
2200
2308
|
if (x >= r2.left && x < r2.right) return i2;
|
|
2201
2309
|
}
|
|
2202
|
-
const firstR = cells[0]?.getBoundingClientRect();
|
|
2203
|
-
const lastR = cells[cells.length - 1]?.getBoundingClientRect();
|
|
2204
|
-
if (firstR && x < firstR.left) return 0;
|
|
2205
|
-
if (lastR && x >= lastR.right) return cells.length - 1;
|
|
2206
2310
|
return -1;
|
|
2207
2311
|
};
|
|
2312
|
+
const bounds = this.scroller.getBoundingClientRect();
|
|
2313
|
+
if (x < bounds.left || x >= bounds.right || y < bounds.top || y >= bounds.bottom) return null;
|
|
2208
2314
|
if (this.allDayRow) {
|
|
2209
2315
|
const r2 = this.allDayRow.getBoundingClientRect();
|
|
2210
2316
|
if (y >= r2.top && y < r2.bottom || !this.body && y >= r2.top) {
|
|
@@ -2269,7 +2375,9 @@ var TimeGridView = class {
|
|
|
2269
2375
|
scrollToTime(minute) {
|
|
2270
2376
|
if (!this.body) return;
|
|
2271
2377
|
const y = (minute - this.ctx.slot.min) * this.ppm;
|
|
2272
|
-
|
|
2378
|
+
const label = this.gutter?.querySelector(".tnc-slot-label");
|
|
2379
|
+
const inset = label ? Math.ceil(label.getBoundingClientRect().height / 2) + 2 : 0;
|
|
2380
|
+
this.scroller.scrollTop = Math.max(0, y - inset);
|
|
2273
2381
|
}
|
|
2274
2382
|
getScroll() {
|
|
2275
2383
|
return { top: this.scroller.scrollTop, left: this.scroller.scrollLeft };
|
|
@@ -2927,6 +3035,14 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2927
3035
|
setScroll(s) {
|
|
2928
3036
|
this.scroller.scrollTop = s.top;
|
|
2929
3037
|
}
|
|
3038
|
+
revealDay(day) {
|
|
3039
|
+
const month = this.cells.find((cell) => cell.day === day)?.el.closest(".tnc-ym");
|
|
3040
|
+
if (!month) return;
|
|
3041
|
+
const bounds = month.getBoundingClientRect();
|
|
3042
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
3043
|
+
if (bounds.top < viewport.top) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
3044
|
+
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
3045
|
+
}
|
|
2930
3046
|
elementsFor(_id) {
|
|
2931
3047
|
return [];
|
|
2932
3048
|
}
|
|
@@ -2939,6 +3055,8 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2939
3055
|
var EXTERNAL_ID = "__tnc_external__";
|
|
2940
3056
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
2941
3057
|
var MAX_AGENDA_DAYS = 366;
|
|
3058
|
+
var PINCH_MIN_SLOT = 16;
|
|
3059
|
+
var PINCH_MAX_SLOT = 96;
|
|
2942
3060
|
function sameOption(a, b, depth = 0) {
|
|
2943
3061
|
if (a === b) return true;
|
|
2944
3062
|
if (typeof a === "function" && typeof b === "function") return true;
|
|
@@ -2966,7 +3084,7 @@ var Calendar = class {
|
|
|
2966
3084
|
this.win = this.doc.defaultView ?? globalThis;
|
|
2967
3085
|
this.opts = { ...defaultOptions(), ...options };
|
|
2968
3086
|
this.zone = getTimeZone(this.opts.timeZone);
|
|
2969
|
-
this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
3087
|
+
this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
2970
3088
|
this.text = this.resolveText();
|
|
2971
3089
|
this.order = parseOrder(this.opts.eventOrder);
|
|
2972
3090
|
this.store = new ItemStore(this.zone, {
|
|
@@ -3006,10 +3124,18 @@ var Calendar = class {
|
|
|
3006
3124
|
conflicts = /* @__PURE__ */ new Map();
|
|
3007
3125
|
slot = { min: 0, max: MINUTES_PER_DAY, duration: 30, snap: 30, scroll: 480 };
|
|
3008
3126
|
draggingId = null;
|
|
3127
|
+
listDragPreview = null;
|
|
3128
|
+
dragSources = [];
|
|
3129
|
+
dragWidth = null;
|
|
3130
|
+
dragScrollFrame = null;
|
|
3131
|
+
dragScrollOrigins = /* @__PURE__ */ new Map();
|
|
3132
|
+
dragScrollEdges = /* @__PURE__ */ new Map();
|
|
3133
|
+
itemWriteStates = /* @__PURE__ */ new Map();
|
|
3009
3134
|
dragRow = null;
|
|
3010
3135
|
strip = null;
|
|
3011
3136
|
stripSize = null;
|
|
3012
3137
|
rebuilding = false;
|
|
3138
|
+
pinch = null;
|
|
3013
3139
|
toolbarEl;
|
|
3014
3140
|
viewHost;
|
|
3015
3141
|
current = null;
|
|
@@ -3019,6 +3145,7 @@ var Calendar = class {
|
|
|
3019
3145
|
session = null;
|
|
3020
3146
|
selection = null;
|
|
3021
3147
|
suppressClick = false;
|
|
3148
|
+
cancelledPointerId = null;
|
|
3022
3149
|
selectedIds = /* @__PURE__ */ new Set();
|
|
3023
3150
|
popover = null;
|
|
3024
3151
|
timer = null;
|
|
@@ -3036,6 +3163,7 @@ var Calendar = class {
|
|
|
3036
3163
|
this.positionMore();
|
|
3037
3164
|
});
|
|
3038
3165
|
this.resizeObs.observe(this.root);
|
|
3166
|
+
this.resizeObs.observe(this.toolbarEl);
|
|
3039
3167
|
}
|
|
3040
3168
|
}
|
|
3041
3169
|
onAdoptCheck = () => {
|
|
@@ -3116,7 +3244,7 @@ var Calendar = class {
|
|
|
3116
3244
|
defaultAllDayDays: this.opts.defaultAllDayDays
|
|
3117
3245
|
});
|
|
3118
3246
|
}
|
|
3119
|
-
if (has("locale") || has("hour12")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
3247
|
+
if (has("locale") || has("hour12") || has("formatTime")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
3120
3248
|
if (has("text") || has("locale")) this.text = this.resolveText();
|
|
3121
3249
|
if (has("eventOrder")) this.order = parseOrder(this.opts.eventOrder);
|
|
3122
3250
|
if (has("date")) this.anchor = this.toDay(this.opts.date) ?? this.anchor;
|
|
@@ -3162,7 +3290,12 @@ var Calendar = class {
|
|
|
3162
3290
|
gotoDate(date) {
|
|
3163
3291
|
const day = this.toDay(date);
|
|
3164
3292
|
if (day === null) return;
|
|
3293
|
+
const sameMonth = this.opts.view === "month" && startOfMonth(day) === startOfMonth(this.anchor);
|
|
3165
3294
|
this.anchor = day;
|
|
3295
|
+
if (sameMonth) {
|
|
3296
|
+
this.view = { ...this.view, anchor: day };
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3166
3299
|
this.rebuild(true);
|
|
3167
3300
|
}
|
|
3168
3301
|
prev() {
|
|
@@ -3308,7 +3441,8 @@ var Calendar = class {
|
|
|
3308
3441
|
out.push(...classList(this.opts.dayClassNames?.(this.dayArg(day))));
|
|
3309
3442
|
return out;
|
|
3310
3443
|
}
|
|
3311
|
-
isEditable(n, what) {
|
|
3444
|
+
isEditable(n, what, presentation = false) {
|
|
3445
|
+
if (!presentation && this.itemWriteStates.get(n.id) === "pending") return false;
|
|
3312
3446
|
if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
|
|
3313
3447
|
if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
|
|
3314
3448
|
const it = n.item;
|
|
@@ -3348,13 +3482,17 @@ var Calendar = class {
|
|
|
3348
3482
|
if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
|
|
3349
3483
|
if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
|
|
3350
3484
|
const indicator = variant === "indicator";
|
|
3351
|
-
if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
|
|
3352
|
-
if (this.draggingId === n.id)
|
|
3485
|
+
if (!preview && !indicator && (this.isEditable(n, "start", true) || this.isEditable(n, "duration", true))) el.classList.add("is-draggable");
|
|
3486
|
+
if (this.draggingId === n.id) {
|
|
3487
|
+
el.classList.add("is-dragging");
|
|
3488
|
+
if (variant === "timed" && this.dragWidth !== null) el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
3489
|
+
}
|
|
3353
3490
|
if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
|
|
3354
3491
|
el.classList.add(...classList(it.className));
|
|
3355
3492
|
const arg = this.itemArg(el, meta);
|
|
3356
3493
|
el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
|
|
3357
3494
|
el.dataset.id = n.id;
|
|
3495
|
+
this.applyItemWriteState(el, n.id);
|
|
3358
3496
|
if (it.kind) el.dataset.kind = it.kind;
|
|
3359
3497
|
const color = it.color ?? it.borderColor;
|
|
3360
3498
|
if (color) el.style.setProperty("--tnc-c", color);
|
|
@@ -3371,6 +3509,7 @@ var Calendar = class {
|
|
|
3371
3509
|
const custom = variant === "list" ? this.opts.listItemContent?.(arg) : null;
|
|
3372
3510
|
const content = indicator ? null : custom ?? this.opts.itemContent?.(arg);
|
|
3373
3511
|
if (indicator) {
|
|
3512
|
+
if (this.opts.monthDisplay === "chips") el.append(h(this.doc, "span", "tnc-item-title", null, it.title));
|
|
3374
3513
|
} else if (content != null) {
|
|
3375
3514
|
const wrap = h(this.doc, "div", "tnc-item-main");
|
|
3376
3515
|
wrap.append(content);
|
|
@@ -3383,6 +3522,7 @@ var Calendar = class {
|
|
|
3383
3522
|
return el;
|
|
3384
3523
|
}
|
|
3385
3524
|
refreshItemEl(el, meta) {
|
|
3525
|
+
this.applyItemWriteState(el, meta.n.id);
|
|
3386
3526
|
const time = el.querySelector(":scope .tnc-item-time");
|
|
3387
3527
|
if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
|
|
3388
3528
|
el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
|
|
@@ -3565,6 +3705,7 @@ var Calendar = class {
|
|
|
3565
3705
|
this.current.scrollToTime(this.slot.scroll);
|
|
3566
3706
|
this.current.revealDay(this.anchor);
|
|
3567
3707
|
} else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
3708
|
+
else if (this.current instanceof YearView) this.current.revealDay(this.anchor);
|
|
3568
3709
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
3569
3710
|
}
|
|
3570
3711
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -3590,9 +3731,11 @@ var Calendar = class {
|
|
|
3590
3731
|
}
|
|
3591
3732
|
/** The range, run on to the end of the strip's days so their dots are right. */
|
|
3592
3733
|
dataRangeFor(range) {
|
|
3593
|
-
const
|
|
3594
|
-
|
|
3595
|
-
|
|
3734
|
+
const window = this.strip?.window();
|
|
3735
|
+
const start = window ? Math.min(range.startDay, window.start) : range.startDay;
|
|
3736
|
+
const end = window ? Math.max(range.endDay, window.end) : range.endDay;
|
|
3737
|
+
if (start === range.startDay && end === range.endDay) return range;
|
|
3738
|
+
return { ...range, startDay: start, start: new Date(toInstant(start, 0, this.zone)), endDay: end, end: new Date(toInstant(end, 0, this.zone)) };
|
|
3596
3739
|
}
|
|
3597
3740
|
/** Adds, updates or removes the date strip for the current view. */
|
|
3598
3741
|
syncStrip(navigated) {
|
|
@@ -3611,7 +3754,9 @@ var Calendar = class {
|
|
|
3611
3754
|
this.root.classList.add("tnc--has-strip");
|
|
3612
3755
|
const { startDay, endDay } = this.view.range;
|
|
3613
3756
|
if (!this.strip) {
|
|
3614
|
-
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown())
|
|
3757
|
+
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown(), (day) => {
|
|
3758
|
+
if (this.agendaList()) this.showListDay(day);
|
|
3759
|
+
});
|
|
3615
3760
|
this.strip = strip;
|
|
3616
3761
|
this.root.insertBefore(strip.el, this.viewHost);
|
|
3617
3762
|
const RO = this.win.ResizeObserver;
|
|
@@ -3639,20 +3784,24 @@ var Calendar = class {
|
|
|
3639
3784
|
stripShown() {
|
|
3640
3785
|
if (!this.strip || this.rebuilding || !this.view) return;
|
|
3641
3786
|
const next = this.dataRangeFor(this.view.range);
|
|
3642
|
-
if (next.endDay === this.view.dataRange.endDay) return;
|
|
3787
|
+
if (next.startDay === this.view.dataRange.startDay && next.endDay === this.view.dataRange.endDay) return;
|
|
3643
3788
|
this.view = { ...this.view, dataRange: next };
|
|
3644
3789
|
this.opts.onRangeChange?.(this.view);
|
|
3645
3790
|
}
|
|
3646
3791
|
/** What is under a point, the date strip included. */
|
|
3647
3792
|
hitAt(x, y) {
|
|
3793
|
+
const target = this.doc.elementFromPoint(x, y);
|
|
3794
|
+
if (!target || !this.root.contains(target)) {
|
|
3795
|
+
this.strip?.markTarget(null);
|
|
3796
|
+
return null;
|
|
3797
|
+
}
|
|
3648
3798
|
const strip = this.strip;
|
|
3649
3799
|
if (strip) {
|
|
3650
|
-
const
|
|
3651
|
-
const day = el ? strip.dayAt(el) : null;
|
|
3800
|
+
const day = strip.dayAt(target);
|
|
3652
3801
|
strip.markTarget(day !== null && this.draggingId ? day : null);
|
|
3653
3802
|
if (day !== null) return { kind: "day", day };
|
|
3654
3803
|
}
|
|
3655
|
-
return this.current?.hit(x, y)
|
|
3804
|
+
return this.current?.el.contains(target) ? this.current.hit(x, y) : null;
|
|
3656
3805
|
}
|
|
3657
3806
|
agendaList() {
|
|
3658
3807
|
return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
|
|
@@ -3766,11 +3915,13 @@ var Calendar = class {
|
|
|
3766
3915
|
buttons.style.display = "flex";
|
|
3767
3916
|
select.style.display = "none";
|
|
3768
3917
|
const startWidth = Array.from(start.children).reduce((width, child) => width + child.scrollWidth, 0);
|
|
3769
|
-
const
|
|
3918
|
+
const availableWidth = Math.min(this.root.clientWidth, this.toolbarEl.clientWidth);
|
|
3919
|
+
const compact = mode === "select" || mode === "auto" && startWidth + buttons.scrollWidth + 32 > availableWidth;
|
|
3770
3920
|
buttons.hidden = compact;
|
|
3771
3921
|
select.hidden = !compact;
|
|
3772
3922
|
buttons.style.display = compact ? "none" : "flex";
|
|
3773
3923
|
select.style.display = compact ? "" : "none";
|
|
3924
|
+
select.parentElement?.classList.toggle("tnc-toolbar-end--compact", compact);
|
|
3774
3925
|
}
|
|
3775
3926
|
applyChange(change) {
|
|
3776
3927
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3835,7 +3986,7 @@ var Calendar = class {
|
|
|
3835
3986
|
const s = wallTime(n.startMs, this.zone);
|
|
3836
3987
|
const start = this.fmt.time(s.minute, true);
|
|
3837
3988
|
if (variant === "dot" || variant === "bar" || variant === "indicator") return { start: s.day === day ? start : "", end: "" };
|
|
3838
|
-
if (n.startMs === n.endMs) return { start, end: "" };
|
|
3989
|
+
if (n.startMs === n.endMs || n.item.timeDisplay === "start") return { start, end: "" };
|
|
3839
3990
|
return { start, end: this.fmt.time(wallTime(n.endMs, this.zone).minute, true) };
|
|
3840
3991
|
}
|
|
3841
3992
|
fillTime(el, n, variant, day) {
|
|
@@ -3851,7 +4002,8 @@ var Calendar = class {
|
|
|
3851
4002
|
}
|
|
3852
4003
|
const s = wallTime(n.startMs, this.zone);
|
|
3853
4004
|
const e = wallTime(n.endMs, this.zone);
|
|
3854
|
-
|
|
4005
|
+
const start = `${n.item.title}, ${this.fmt.longDate(s.day)} ${this.fmt.time(s.minute)}`;
|
|
4006
|
+
return n.item.timeDisplay === "start" || n.startMs === n.endMs ? start : `${start} \u2013 ${this.fmt.time(e.minute)}`;
|
|
3855
4007
|
}
|
|
3856
4008
|
defaultContent(meta) {
|
|
3857
4009
|
const { n, variant, day } = meta;
|
|
@@ -3879,7 +4031,7 @@ var Calendar = class {
|
|
|
3879
4031
|
addHandles(el, meta) {
|
|
3880
4032
|
const { n, variant } = meta;
|
|
3881
4033
|
const ports = variant === "timeline" && !meta.mirror && n.id !== EXTERNAL_ID && !!this.opts.timeline?.onLink;
|
|
3882
|
-
if (variant === "dot" || !this.isEditable(n, "duration")) {
|
|
4034
|
+
if (variant === "dot" || !this.isEditable(n, "duration", true)) {
|
|
3883
4035
|
if (ports) this.addPorts(el, n);
|
|
3884
4036
|
return;
|
|
3885
4037
|
}
|
|
@@ -3968,7 +4120,10 @@ var Calendar = class {
|
|
|
3968
4120
|
r.addEventListener("dragover", this.onNativeDragOver);
|
|
3969
4121
|
r.addEventListener("dragleave", this.onNativeDragLeave);
|
|
3970
4122
|
r.addEventListener("drop", this.onNativeDrop);
|
|
4123
|
+
r.addEventListener("touchstart", this.onTouchStart, { passive: true });
|
|
3971
4124
|
r.addEventListener("touchmove", this.onTouchMove, { passive: false });
|
|
4125
|
+
r.addEventListener("touchend", this.onTouchEnd);
|
|
4126
|
+
r.addEventListener("touchcancel", this.onTouchEnd);
|
|
3972
4127
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
3973
4128
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
3974
4129
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -3987,7 +4142,10 @@ var Calendar = class {
|
|
|
3987
4142
|
r.removeEventListener("dragover", this.onNativeDragOver);
|
|
3988
4143
|
r.removeEventListener("dragleave", this.onNativeDragLeave);
|
|
3989
4144
|
r.removeEventListener("drop", this.onNativeDrop);
|
|
4145
|
+
r.removeEventListener("touchstart", this.onTouchStart);
|
|
3990
4146
|
r.removeEventListener("touchmove", this.onTouchMove);
|
|
4147
|
+
r.removeEventListener("touchend", this.onTouchEnd);
|
|
4148
|
+
r.removeEventListener("touchcancel", this.onTouchEnd);
|
|
3991
4149
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
3992
4150
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
3993
4151
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -4007,6 +4165,10 @@ var Calendar = class {
|
|
|
4007
4165
|
return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
|
|
4008
4166
|
}
|
|
4009
4167
|
onDocPointerDown = (ev) => {
|
|
4168
|
+
if (this.cancelledPointerId !== null) {
|
|
4169
|
+
this.cancelledPointerId = null;
|
|
4170
|
+
this.listenDoc(false);
|
|
4171
|
+
}
|
|
4010
4172
|
const t = ev.target;
|
|
4011
4173
|
if (this.popover && !this.popover.el.contains(t) && !this.popover.anchor.contains(t)) this.closeMore();
|
|
4012
4174
|
if (this.selection && !this.root.contains(t)) this.unselect();
|
|
@@ -4014,6 +4176,7 @@ var Calendar = class {
|
|
|
4014
4176
|
onPointerDown = (ev) => {
|
|
4015
4177
|
if (ev.button !== 0 || this.session) return;
|
|
4016
4178
|
const target = ev.target;
|
|
4179
|
+
if (target.closest(".tnc-dg--compact .tnc-dg-day")) return;
|
|
4017
4180
|
if (target.closest("button, a, input, select, textarea, .tnc-toolbar, .tnc-port, [data-tnc-no-drag]")) {
|
|
4018
4181
|
if (!target.closest(".tnc-item")) return;
|
|
4019
4182
|
if (target.closest(".tnc-port, [data-tnc-no-drag], input, select, textarea")) return;
|
|
@@ -4046,7 +4209,7 @@ var Calendar = class {
|
|
|
4046
4209
|
y0: ev.clientY,
|
|
4047
4210
|
active: false,
|
|
4048
4211
|
origin,
|
|
4049
|
-
grab: overflowOrigin
|
|
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,
|
|
4050
4213
|
proposal: null,
|
|
4051
4214
|
denied: false,
|
|
4052
4215
|
timer: null,
|
|
@@ -4071,20 +4234,159 @@ var Calendar = class {
|
|
|
4071
4234
|
};
|
|
4072
4235
|
activate(s) {
|
|
4073
4236
|
s.active = true;
|
|
4237
|
+
if (s.lastEvent) this.leaveHover(s.lastEvent);
|
|
4074
4238
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
4075
4239
|
s.timer = null;
|
|
4076
4240
|
if (s.origin) {
|
|
4077
4241
|
this.draggingId = s.origin.id;
|
|
4078
|
-
for (const el of this.elementsFor(s.origin.id))
|
|
4242
|
+
for (const el of this.elementsFor(s.origin.id)) {
|
|
4243
|
+
if (this.meta.get(el)?.variant === "timed" && s.mode === "move") {
|
|
4244
|
+
this.dragWidth = el.getBoundingClientRect().width;
|
|
4245
|
+
el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
4246
|
+
}
|
|
4247
|
+
if (this.opts.view !== "list" && !(this.current instanceof DayGridView) && s.mode === "move") {
|
|
4248
|
+
const source = el.cloneNode(true);
|
|
4249
|
+
source.classList.remove("is-selected");
|
|
4250
|
+
source.classList.add("tnc-drag-source");
|
|
4251
|
+
source.setAttribute("aria-hidden", "true");
|
|
4252
|
+
source.inert = true;
|
|
4253
|
+
for (const node of [source, ...source.querySelectorAll("[id], [data-id], [data-task-path]")]) {
|
|
4254
|
+
node.removeAttribute("id");
|
|
4255
|
+
node.removeAttribute("data-id");
|
|
4256
|
+
node.removeAttribute("data-task-path");
|
|
4257
|
+
}
|
|
4258
|
+
el.before(source);
|
|
4259
|
+
this.dragSources.push(source);
|
|
4260
|
+
}
|
|
4261
|
+
if (this.opts.view === "list" && !this.listDragPreview && s.mode === "move") {
|
|
4262
|
+
const bounds = el.getBoundingClientRect();
|
|
4263
|
+
const preview = el.cloneNode(true);
|
|
4264
|
+
preview.classList.add("tnc-drag-preview", "is-dragging");
|
|
4265
|
+
preview.removeAttribute("data-id");
|
|
4266
|
+
preview.removeAttribute("data-task-path");
|
|
4267
|
+
preview.setAttribute("aria-hidden", "true");
|
|
4268
|
+
preview.inert = true;
|
|
4269
|
+
preview.style.width = `${bounds.width}px`;
|
|
4270
|
+
this.root.append(preview);
|
|
4271
|
+
this.listDragPreview = { element: preview, offsetX: s.x0 - bounds.left, offsetY: s.y0 - bounds.top, width: bounds.width, height: bounds.height };
|
|
4272
|
+
}
|
|
4273
|
+
el.classList.add("is-dragging");
|
|
4274
|
+
}
|
|
4079
4275
|
}
|
|
4080
4276
|
this.closeMore();
|
|
4081
4277
|
if (s.mode !== "select") this.unselectSilently();
|
|
4082
4278
|
this.root.classList.add("tnc-dragging", `tnc-dragging-${s.mode}`);
|
|
4083
4279
|
if (s.lastEvent) this.track(s, s.lastEvent);
|
|
4280
|
+
this.startDragScroll();
|
|
4281
|
+
}
|
|
4282
|
+
startDragScroll() {
|
|
4283
|
+
if (this.dragScrollFrame !== null) return;
|
|
4284
|
+
const tick = () => {
|
|
4285
|
+
this.dragScrollFrame = null;
|
|
4286
|
+
const session = this.session;
|
|
4287
|
+
const pointer = session?.lastEvent;
|
|
4288
|
+
if (!session?.active || !pointer) return;
|
|
4289
|
+
const origin = session.origin ? this.elementsFor(session.origin.id)[0] : this.viewHost;
|
|
4290
|
+
const underPointer = this.doc.elementFromPoint(pointer.clientX, pointer.clientY);
|
|
4291
|
+
let moved = false;
|
|
4292
|
+
if (origin && underPointer && this.root.contains(underPointer)) {
|
|
4293
|
+
for (const axis of ["x", "y"]) {
|
|
4294
|
+
const scroller = scrollerOf(origin, axis);
|
|
4295
|
+
if (!scroller) continue;
|
|
4296
|
+
const viewport = scroller === this.doc.scrollingElement ? { left: 0, top: 0, right: this.win.innerWidth, bottom: this.win.innerHeight } : scroller.getBoundingClientRect();
|
|
4297
|
+
const coordinate = axis === "x" ? pointer.clientX : pointer.clientY;
|
|
4298
|
+
const lower = Math.max(0, axis === "x" ? viewport.left : viewport.top);
|
|
4299
|
+
const upper = Math.min(axis === "x" ? this.win.innerWidth : this.win.innerHeight, axis === "x" ? viewport.right : viewport.bottom);
|
|
4300
|
+
if (coordinate < lower || coordinate > upper) {
|
|
4301
|
+
this.dragScrollEdges.delete(axis);
|
|
4302
|
+
continue;
|
|
4303
|
+
}
|
|
4304
|
+
const delta = coordinate < lower + 32 ? -Math.min(12, (lower + 32 - coordinate) / 2) : coordinate > upper - 32 ? Math.min(12, (coordinate - upper + 32) / 2) : 0;
|
|
4305
|
+
if (!delta) {
|
|
4306
|
+
this.dragScrollEdges.delete(axis);
|
|
4307
|
+
continue;
|
|
4308
|
+
}
|
|
4309
|
+
const edge = this.dragScrollEdges.get(axis);
|
|
4310
|
+
const direction = Math.sign(delta);
|
|
4311
|
+
const now = this.win.performance.now();
|
|
4312
|
+
if (!edge || edge.scroller !== scroller || edge.direction !== direction) {
|
|
4313
|
+
this.dragScrollEdges.set(axis, { scroller, direction, since: now });
|
|
4314
|
+
continue;
|
|
4315
|
+
}
|
|
4316
|
+
if (now - edge.since < 180) continue;
|
|
4317
|
+
if (!this.dragScrollOrigins.has(scroller)) this.dragScrollOrigins.set(scroller, { top: scroller.scrollTop, left: scroller.scrollLeft });
|
|
4318
|
+
const before = axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
|
|
4319
|
+
if (axis === "x") scroller.scrollLeft += delta;
|
|
4320
|
+
else scroller.scrollTop += delta;
|
|
4321
|
+
moved ||= before !== (axis === "x" ? scroller.scrollLeft : scroller.scrollTop);
|
|
4322
|
+
}
|
|
4323
|
+
} else this.dragScrollEdges.clear();
|
|
4324
|
+
if (moved) this.track(session, pointer);
|
|
4325
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4326
|
+
};
|
|
4327
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4084
4328
|
}
|
|
4329
|
+
onTouchStart = (ev) => {
|
|
4330
|
+
const first = ev.touches[0];
|
|
4331
|
+
const second = ev.touches[1];
|
|
4332
|
+
if (!first || !second) {
|
|
4333
|
+
this.endPinch();
|
|
4334
|
+
return;
|
|
4335
|
+
}
|
|
4336
|
+
const view = this.current;
|
|
4337
|
+
if (this.session?.active || !this.opts.pinchZoom || !(view instanceof TimeGridView)) return;
|
|
4338
|
+
const body = first.target.closest(".tnc-tg-body");
|
|
4339
|
+
if (!body?.contains(second.target)) return;
|
|
4340
|
+
this.endSession(null, true);
|
|
4341
|
+
if (this.pinch) return;
|
|
4342
|
+
const { top, height } = body.getBoundingClientRect();
|
|
4343
|
+
const y = (first.clientY + second.clientY) / 2;
|
|
4344
|
+
this.pinch = {
|
|
4345
|
+
body,
|
|
4346
|
+
distance: Math.max(1, Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY)),
|
|
4347
|
+
y,
|
|
4348
|
+
anchor: (y - top) / height,
|
|
4349
|
+
drawn: view.drawnSlotHeight,
|
|
4350
|
+
option: this.opts.slotHeight
|
|
4351
|
+
};
|
|
4352
|
+
};
|
|
4085
4353
|
onTouchMove = (ev) => {
|
|
4086
4354
|
if (this.session?.active) ev.preventDefault();
|
|
4355
|
+
const p = this.pinch;
|
|
4356
|
+
const first = ev.touches[0];
|
|
4357
|
+
const second = ev.touches[1];
|
|
4358
|
+
if (!p || !first || !second) return;
|
|
4359
|
+
if (!ev.cancelable) {
|
|
4360
|
+
this.endPinch();
|
|
4361
|
+
return;
|
|
4362
|
+
}
|
|
4363
|
+
ev.preventDefault();
|
|
4364
|
+
const height = p.drawn * Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY) / p.distance;
|
|
4365
|
+
p.y = (first.clientY + second.clientY) / 2;
|
|
4366
|
+
this.pinchTo(p, Math.min(Math.max(p.drawn, PINCH_MAX_SLOT), Math.max(Math.min(p.drawn, PINCH_MIN_SLOT), height)));
|
|
4367
|
+
};
|
|
4368
|
+
onTouchEnd = (ev) => {
|
|
4369
|
+
if (ev.touches.length < 2) this.endPinch();
|
|
4087
4370
|
};
|
|
4371
|
+
/** Rescales the grid, keeping the anchored time under the fingers. */
|
|
4372
|
+
pinchTo(p, slotHeight) {
|
|
4373
|
+
this.opts = { ...this.opts, slotHeight };
|
|
4374
|
+
if (!(this.current instanceof TimeGridView)) return;
|
|
4375
|
+
this.current.applyScale();
|
|
4376
|
+
const { top, height } = p.body.getBoundingClientRect();
|
|
4377
|
+
const scroller = scrollerOf(p.body, "y");
|
|
4378
|
+
if (scroller) scroller.scrollTop += top + height * p.anchor - p.y;
|
|
4379
|
+
}
|
|
4380
|
+
endPinch() {
|
|
4381
|
+
const p = this.pinch;
|
|
4382
|
+
this.pinch = null;
|
|
4383
|
+
if (!p?.body.isConnected) return;
|
|
4384
|
+
const settled = Math.round(this.opts.slotHeight);
|
|
4385
|
+
const slotHeight = settled === Math.round(p.drawn) ? p.option : settled;
|
|
4386
|
+
this.pinchTo(p, slotHeight);
|
|
4387
|
+
this.current?.render(null);
|
|
4388
|
+
if (slotHeight !== p.option) this.opts.onSlotHeightChange?.(slotHeight);
|
|
4389
|
+
}
|
|
4088
4390
|
onPointerMove = (ev) => {
|
|
4089
4391
|
const s = this.session;
|
|
4090
4392
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
@@ -4110,6 +4412,12 @@ var Calendar = class {
|
|
|
4110
4412
|
this.track(s, ev);
|
|
4111
4413
|
};
|
|
4112
4414
|
onPointerUp = (ev) => {
|
|
4415
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
4416
|
+
this.cancelledPointerId = null;
|
|
4417
|
+
this.suppressNextClick();
|
|
4418
|
+
this.listenDoc(false);
|
|
4419
|
+
return;
|
|
4420
|
+
}
|
|
4113
4421
|
const s = this.session;
|
|
4114
4422
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
4115
4423
|
if (!s.active) {
|
|
@@ -4122,28 +4430,49 @@ var Calendar = class {
|
|
|
4122
4430
|
return;
|
|
4123
4431
|
}
|
|
4124
4432
|
this.suppressNextClick();
|
|
4433
|
+
this.track(s, ev);
|
|
4125
4434
|
this.endSession(ev, false);
|
|
4126
4435
|
};
|
|
4127
4436
|
onPointerCancel = (ev) => {
|
|
4437
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
4438
|
+
this.cancelledPointerId = null;
|
|
4439
|
+
this.listenDoc(false);
|
|
4440
|
+
}
|
|
4128
4441
|
if (this.session && ev.pointerId === this.session.pointerId) this.endSession(null, true);
|
|
4129
4442
|
};
|
|
4130
4443
|
onDocKeyDown = (ev) => {
|
|
4131
4444
|
if (ev.key === "Escape" && this.session) {
|
|
4132
4445
|
ev.preventDefault();
|
|
4133
4446
|
ev.stopPropagation();
|
|
4447
|
+
const pointerId = this.session.active ? this.session.pointerId : null;
|
|
4134
4448
|
this.endSession(null, true);
|
|
4449
|
+
if (pointerId !== null) {
|
|
4450
|
+
this.cancelledPointerId = pointerId;
|
|
4451
|
+
this.listenDoc(true);
|
|
4452
|
+
}
|
|
4135
4453
|
}
|
|
4136
4454
|
};
|
|
4137
4455
|
/** Proposal for the current pointer position. */
|
|
4138
4456
|
track(s, ev) {
|
|
4457
|
+
if (this.listDragPreview) {
|
|
4458
|
+
const preview = this.listDragPreview;
|
|
4459
|
+
const bounds = this.root.getBoundingClientRect();
|
|
4460
|
+
preview.element.style.left = `${Math.max(8, Math.min(this.win.innerWidth - preview.width - 8, ev.clientX - preview.offsetX)) - bounds.left}px`;
|
|
4461
|
+
preview.element.style.top = `${Math.max(8, Math.min(this.win.innerHeight - preview.height - 8, ev.clientY - preview.offsetY)) - bounds.top}px`;
|
|
4462
|
+
}
|
|
4139
4463
|
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4140
4464
|
if (s.mode === "external") {
|
|
4141
4465
|
this.trackExternal(s, hit);
|
|
4142
4466
|
return;
|
|
4143
4467
|
}
|
|
4144
4468
|
if (!s.grab) s.grab = hit;
|
|
4145
|
-
if (!hit || !s.grab)
|
|
4469
|
+
if (!hit || !s.grab) {
|
|
4470
|
+
this.invalidateProposal(s);
|
|
4471
|
+
return;
|
|
4472
|
+
}
|
|
4146
4473
|
if (s.mode === "select") {
|
|
4474
|
+
s.denied = false;
|
|
4475
|
+
this.root.classList.remove("tnc-drop-denied");
|
|
4147
4476
|
const sel = this.selectionFor(s.grab, hit);
|
|
4148
4477
|
if (!sel) return;
|
|
4149
4478
|
this.selection = sel;
|
|
@@ -4152,25 +4481,40 @@ var Calendar = class {
|
|
|
4152
4481
|
}
|
|
4153
4482
|
const origin = s.origin;
|
|
4154
4483
|
const p = s.mode === "move" ? this.moveProposal(origin, s.grab, hit) : this.resizeProposal(origin, hit, s.mode === "resize-start");
|
|
4155
|
-
if (!p)
|
|
4484
|
+
if (!p) {
|
|
4485
|
+
this.invalidateProposal(s);
|
|
4486
|
+
return;
|
|
4487
|
+
}
|
|
4156
4488
|
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;
|
|
4157
4489
|
s.denied = !allowed;
|
|
4158
4490
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
4159
|
-
if (!allowed
|
|
4491
|
+
if (!allowed) {
|
|
4492
|
+
this.invalidateProposal(s);
|
|
4493
|
+
return;
|
|
4494
|
+
}
|
|
4495
|
+
if (sameProposal(p, s.proposal)) return;
|
|
4160
4496
|
s.proposal = p;
|
|
4161
4497
|
this.dragRow = p.rowId !== void 0 ? { id: origin.id, rowId: p.rowId } : null;
|
|
4162
|
-
this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4498
|
+
if (this.opts.view !== "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4499
|
+
}
|
|
4500
|
+
invalidateProposal(s) {
|
|
4501
|
+
const hadProposal = s.proposal !== null;
|
|
4502
|
+
s.proposal = null;
|
|
4503
|
+
s.hit = null;
|
|
4504
|
+
s.denied = true;
|
|
4505
|
+
this.dragRow = null;
|
|
4506
|
+
this.root.classList.add("tnc-drop-denied");
|
|
4507
|
+
this.strip?.markTarget(null);
|
|
4508
|
+
this.unselectSilently();
|
|
4509
|
+
if (s.mode === "external") this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
4510
|
+
else if (hadProposal && s.origin) this.applyChange(this.store.upsert([s.origin.item]));
|
|
4163
4511
|
}
|
|
4164
4512
|
trackExternal(s, raw) {
|
|
4165
4513
|
const src = s.source;
|
|
4166
4514
|
const hit = raw?.kind === "timeline" && raw.area !== "track" && raw.area !== "header" ? null : raw;
|
|
4167
4515
|
s.hit = hit;
|
|
4168
4516
|
if (!hit) {
|
|
4169
|
-
|
|
4170
|
-
s.proposal = null;
|
|
4171
|
-
this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
4172
|
-
}
|
|
4173
|
-
this.unselectSilently();
|
|
4517
|
+
this.invalidateProposal(s);
|
|
4174
4518
|
return;
|
|
4175
4519
|
}
|
|
4176
4520
|
const dur = src.durationMinutes ?? this.opts.defaultTimedMinutes;
|
|
@@ -4189,6 +4533,10 @@ var Calendar = class {
|
|
|
4189
4533
|
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;
|
|
4190
4534
|
s.denied = !allowed;
|
|
4191
4535
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
4536
|
+
if (!allowed) {
|
|
4537
|
+
this.invalidateProposal(s);
|
|
4538
|
+
return;
|
|
4539
|
+
}
|
|
4192
4540
|
if (sameProposal(p, s.proposal)) return;
|
|
4193
4541
|
s.proposal = p;
|
|
4194
4542
|
if (hit.kind === "timeline" && p.rowId === void 0) {
|
|
@@ -4381,7 +4729,19 @@ var Calendar = class {
|
|
|
4381
4729
|
endSession(ev, cancel) {
|
|
4382
4730
|
const s = this.session;
|
|
4383
4731
|
if (!s) return;
|
|
4732
|
+
if (this.dragScrollFrame !== null) this.win.cancelAnimationFrame(this.dragScrollFrame);
|
|
4733
|
+
this.dragScrollFrame = null;
|
|
4734
|
+
if (cancel || s.denied) for (const [scroller, position] of this.dragScrollOrigins) {
|
|
4735
|
+
scroller.scrollTop = position.top;
|
|
4736
|
+
scroller.scrollLeft = position.left;
|
|
4737
|
+
}
|
|
4738
|
+
this.dragScrollOrigins.clear();
|
|
4739
|
+
this.dragScrollEdges.clear();
|
|
4384
4740
|
this.session = null;
|
|
4741
|
+
this.listDragPreview?.element.remove();
|
|
4742
|
+
this.listDragPreview = null;
|
|
4743
|
+
for (const source of this.dragSources) source.remove();
|
|
4744
|
+
this.dragSources = [];
|
|
4385
4745
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
4386
4746
|
this.listenDoc(false);
|
|
4387
4747
|
this.root.classList.remove(
|
|
@@ -4395,10 +4755,15 @@ var Calendar = class {
|
|
|
4395
4755
|
);
|
|
4396
4756
|
const draggedId = this.draggingId;
|
|
4397
4757
|
this.draggingId = null;
|
|
4758
|
+
this.dragWidth = null;
|
|
4398
4759
|
this.dragRow = null;
|
|
4399
|
-
if (draggedId) for (const el of this.elementsFor(draggedId))
|
|
4760
|
+
if (draggedId) for (const el of this.elementsFor(draggedId)) {
|
|
4761
|
+
el.classList.remove("is-dragging");
|
|
4762
|
+
el.style.removeProperty("--tnc-drag-width");
|
|
4763
|
+
}
|
|
4400
4764
|
this.strip?.markTarget(null);
|
|
4401
4765
|
if (!s.active) return;
|
|
4766
|
+
if (this.current instanceof DayGridView) this.current.render(null);
|
|
4402
4767
|
if (s.mode === "select") {
|
|
4403
4768
|
if (cancel) this.unselectSilently();
|
|
4404
4769
|
else if (this.selection) this.fireSelect(this.selection, ev);
|
|
@@ -4450,10 +4815,16 @@ var Calendar = class {
|
|
|
4450
4815
|
revert();
|
|
4451
4816
|
return;
|
|
4452
4817
|
}
|
|
4818
|
+
if (this.opts.view === "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4453
4819
|
this.flush();
|
|
4454
4820
|
this.commit(s.mode === "move" ? "drop" : "resize", origin, p, ev, revert);
|
|
4455
4821
|
}
|
|
4456
4822
|
commit(kind, origin, p, ev, revert) {
|
|
4823
|
+
let reverted = false;
|
|
4824
|
+
const mark = (state) => {
|
|
4825
|
+
this.itemWriteStates.set(origin.id, state);
|
|
4826
|
+
for (const element of this.elementsFor(origin.id)) this.applyItemWriteState(element, origin.id);
|
|
4827
|
+
};
|
|
4457
4828
|
const arg = {
|
|
4458
4829
|
item: origin.item,
|
|
4459
4830
|
start: new Date(p.startMs),
|
|
@@ -4469,19 +4840,32 @@ var Calendar = class {
|
|
|
4469
4840
|
jsEvent: ev,
|
|
4470
4841
|
source: ev && "key" in ev ? "keyboard" : "pointer",
|
|
4471
4842
|
view: this.view,
|
|
4472
|
-
revert
|
|
4843
|
+
revert: () => {
|
|
4844
|
+
reverted = true;
|
|
4845
|
+
revert();
|
|
4846
|
+
mark("failed");
|
|
4847
|
+
}
|
|
4473
4848
|
};
|
|
4474
4849
|
const handler = kind === "drop" ? this.opts.onItemDrop : this.opts.onItemResize;
|
|
4475
4850
|
try {
|
|
4851
|
+
mark("pending");
|
|
4476
4852
|
const res = handler?.(arg);
|
|
4477
4853
|
if (res && typeof res.then === "function") {
|
|
4478
|
-
Promise.resolve(res).
|
|
4479
|
-
|
|
4854
|
+
void Promise.resolve(res).then(() => {
|
|
4855
|
+
if (!reverted) mark("changed");
|
|
4856
|
+
}, () => arg.revert());
|
|
4857
|
+
} else if (!reverted) mark("changed");
|
|
4480
4858
|
} catch (err) {
|
|
4481
|
-
revert();
|
|
4859
|
+
arg.revert();
|
|
4482
4860
|
throw err;
|
|
4483
4861
|
}
|
|
4484
4862
|
}
|
|
4863
|
+
applyItemWriteState(element, id) {
|
|
4864
|
+
const state = this.itemWriteStates.get(id);
|
|
4865
|
+
for (const name of ["pending", "failed", "changed"]) element.classList.toggle(`is-write-${name}`, state === name);
|
|
4866
|
+
if (state === "pending") element.setAttribute("aria-busy", "true");
|
|
4867
|
+
else element.removeAttribute("aria-busy");
|
|
4868
|
+
}
|
|
4485
4869
|
/* --------------------------------------------------------- click / keys */
|
|
4486
4870
|
/** Runs in the capture phase so the click ending a drag never reaches content inside the calendar, React portals included. */
|
|
4487
4871
|
onClickCapture = (ev) => {
|
|
@@ -4491,6 +4875,12 @@ var Calendar = class {
|
|
|
4491
4875
|
ev.preventDefault();
|
|
4492
4876
|
};
|
|
4493
4877
|
onClick = (ev) => {
|
|
4878
|
+
const compactDay = ev.target.closest?.(".tnc-dg--compact .tnc-dg-day");
|
|
4879
|
+
if (compactDay && this.root.contains(compactDay) && !ev.target.closest(".tnc-navlink")) {
|
|
4880
|
+
const day = parseDay(compactDay.dataset.date ?? "");
|
|
4881
|
+
if (day !== null) this.openMore(day, compactDay, ev);
|
|
4882
|
+
return;
|
|
4883
|
+
}
|
|
4494
4884
|
const yearDay = ev.target.closest?.(".tnc-ym-day");
|
|
4495
4885
|
if (yearDay && this.root.contains(yearDay)) {
|
|
4496
4886
|
const day = parseDay(yearDay.dataset.date ?? "");
|
|
@@ -4579,7 +4969,8 @@ var Calendar = class {
|
|
|
4579
4969
|
const day = parseDay(target.dataset.date ?? "");
|
|
4580
4970
|
if (day !== null) {
|
|
4581
4971
|
ev.preventDefault();
|
|
4582
|
-
|
|
4972
|
+
if (target.closest(".tnc-dg--compact")) this.openMore(day, target, ev);
|
|
4973
|
+
else this.clickSlot({ kind: "day", day }, ev, true);
|
|
4583
4974
|
}
|
|
4584
4975
|
return;
|
|
4585
4976
|
}
|
|
@@ -4667,9 +5058,13 @@ var Calendar = class {
|
|
|
4667
5058
|
const dt = ev.dataTransfer;
|
|
4668
5059
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
4669
5060
|
ev.preventDefault();
|
|
5061
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
5062
|
+
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") {
|
|
5063
|
+
dt.dropEffect = "none";
|
|
5064
|
+
this.unselectSilently();
|
|
5065
|
+
return;
|
|
5066
|
+
}
|
|
4670
5067
|
dt.dropEffect = "copy";
|
|
4671
|
-
const hit = this.current.hit(ev.clientX, ev.clientY);
|
|
4672
|
-
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
4673
5068
|
const sel = this.selectionFor(hit, hit);
|
|
4674
5069
|
if (!sel) return;
|
|
4675
5070
|
this.selection = sel;
|
|
@@ -4683,7 +5078,7 @@ var Calendar = class {
|
|
|
4683
5078
|
const dt = ev.dataTransfer;
|
|
4684
5079
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
4685
5080
|
ev.preventDefault();
|
|
4686
|
-
const hit = this.
|
|
5081
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4687
5082
|
this.unselectSilently();
|
|
4688
5083
|
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
4689
5084
|
const sel = this.selectionFor(hit, hit);
|
|
@@ -4714,7 +5109,7 @@ function scrollerOf(el, axis) {
|
|
|
4714
5109
|
return doc.scrollingElement;
|
|
4715
5110
|
}
|
|
4716
5111
|
function rangeMoved(prev, next) {
|
|
4717
|
-
return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.endDay !== next.dataRange.endDay;
|
|
5112
|
+
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;
|
|
4718
5113
|
}
|
|
4719
5114
|
|
|
4720
5115
|
// src/react/index.tsx
|