@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/index.cjs
CHANGED
|
@@ -832,14 +832,16 @@ function pct(n) {
|
|
|
832
832
|
|
|
833
833
|
// src/dom/format.ts
|
|
834
834
|
var Formatter = class {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
hour12;
|
|
838
|
-
constructor(locale, hour12) {
|
|
835
|
+
constructor(locale, hour12, formatTime) {
|
|
836
|
+
this.formatTime = formatTime;
|
|
839
837
|
this.locale = locale;
|
|
840
838
|
const probe = new Intl.DateTimeFormat(locale, { hour: "numeric" }).resolvedOptions();
|
|
841
839
|
this.hour12 = hour12 ?? (probe.hour12 === true || probe.hourCycle === "h12" || probe.hourCycle === "h11");
|
|
842
840
|
}
|
|
841
|
+
formatTime;
|
|
842
|
+
cache = /* @__PURE__ */ new Map();
|
|
843
|
+
locale;
|
|
844
|
+
hour12;
|
|
843
845
|
fmt(key, opts) {
|
|
844
846
|
let f = this.cache.get(key);
|
|
845
847
|
if (!f) {
|
|
@@ -897,6 +899,7 @@ var Formatter = class {
|
|
|
897
899
|
/** Wall-clock minutes to a label. `compact` drops ":00" in 12-hour formats ("9am"). */
|
|
898
900
|
time(minute, compact = false) {
|
|
899
901
|
const m = (Math.round(minute) % 1440 + 1440) % 1440;
|
|
902
|
+
if (this.formatTime) return this.formatTime(m);
|
|
900
903
|
const date = new Date(Date.UTC(2e3, 0, 1, Math.floor(m / 60), m % 60));
|
|
901
904
|
if (this.hour12) {
|
|
902
905
|
const parts = this.fmt("t12", { hour: "numeric", minute: "2-digit", hour12: true }).formatToParts(date);
|
|
@@ -988,6 +991,7 @@ function defaultOptions() {
|
|
|
988
991
|
snapDuration: void 0,
|
|
989
992
|
scrollTime: "08:00",
|
|
990
993
|
slotHeight: 24,
|
|
994
|
+
pinchZoom: true,
|
|
991
995
|
expandRows: false,
|
|
992
996
|
nowIndicator: true,
|
|
993
997
|
selectMirror: true,
|
|
@@ -1029,6 +1033,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
1029
1033
|
"locale",
|
|
1030
1034
|
"firstDay",
|
|
1031
1035
|
"hour12",
|
|
1036
|
+
"formatTime",
|
|
1032
1037
|
"weekNumbers",
|
|
1033
1038
|
"weekends",
|
|
1034
1039
|
"hiddenDays",
|
|
@@ -1041,9 +1046,11 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
1041
1046
|
"slotDuration",
|
|
1042
1047
|
"snapDuration",
|
|
1043
1048
|
"slotHeight",
|
|
1049
|
+
"pinchZoom",
|
|
1044
1050
|
"expandRows",
|
|
1045
1051
|
"nowIndicator",
|
|
1046
1052
|
"slotEventOverlap",
|
|
1053
|
+
"overlapStackThreshold",
|
|
1047
1054
|
"eventMinHeight",
|
|
1048
1055
|
"eventMaxStack",
|
|
1049
1056
|
"dayMaxEvents",
|
|
@@ -1074,8 +1081,9 @@ var DayGridView = class {
|
|
|
1074
1081
|
constructor(ctx) {
|
|
1075
1082
|
this.ctx = ctx;
|
|
1076
1083
|
const { doc, opts, days } = ctx;
|
|
1077
|
-
this.dotsMode = opts.monthDisplay === "dots";
|
|
1084
|
+
this.dotsMode = opts.monthDisplay === "dots" || opts.monthDisplay === "chips";
|
|
1078
1085
|
this.el = h(doc, "div", this.dotsMode ? "tnc-dg tnc-dg--dots" : "tnc-dg");
|
|
1086
|
+
this.el.classList.toggle("tnc-dg--chips", opts.monthDisplay === "chips");
|
|
1079
1087
|
this.scroller = h(doc, "div", "tnc-dg-scroll");
|
|
1080
1088
|
this.grid = h(doc, "div", "tnc-dg-grid", { role: "grid", "aria-label": ctx.view.title });
|
|
1081
1089
|
const perWeek = this.daysPerWeek();
|
|
@@ -1105,14 +1113,14 @@ var DayGridView = class {
|
|
|
1105
1113
|
const cell = h(doc, "div", cls.join(" "), { role: "gridcell", "aria-label": ctx.fmt.longDate(d), "data-date": arg.dateStr });
|
|
1106
1114
|
if (this.dotsMode) cell.tabIndex = 0;
|
|
1107
1115
|
const num2 = ctx.fmt.dayNumber(d);
|
|
1108
|
-
const label = ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num2}` : num2;
|
|
1116
|
+
const label = !this.dotsMode && ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num2}` : num2;
|
|
1109
1117
|
cell.append(
|
|
1110
1118
|
h(
|
|
1111
1119
|
doc,
|
|
1112
1120
|
"div",
|
|
1113
1121
|
"tnc-dg-top",
|
|
1114
1122
|
null,
|
|
1115
|
-
opts.navLinks ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
1123
|
+
opts.navLinks && !this.dotsMode ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
1116
1124
|
)
|
|
1117
1125
|
);
|
|
1118
1126
|
dates.append(cell);
|
|
@@ -1126,20 +1134,20 @@ var DayGridView = class {
|
|
|
1126
1134
|
layer.append(content);
|
|
1127
1135
|
row.append(dates, layer);
|
|
1128
1136
|
this.grid.append(row);
|
|
1129
|
-
const dots =
|
|
1137
|
+
const dots = cells.map((cell) => {
|
|
1130
1138
|
const box = h(doc, "div", "tnc-dg-dots", { "aria-hidden": "true" });
|
|
1131
1139
|
if (opts.dayMaxEvents === false) box.classList.add("is-unlimited");
|
|
1132
1140
|
const count = h(doc, "span", "tnc-dg-dots-more");
|
|
1133
1141
|
cell.append(box);
|
|
1134
1142
|
return { box, pool: new ItemPool(ctx, box), count };
|
|
1135
|
-
})
|
|
1143
|
+
});
|
|
1136
1144
|
this.weeks.push({ days: wdays, row, cells, layer, sel, more, pool: new ItemPool(ctx, content), dots });
|
|
1137
1145
|
}
|
|
1138
1146
|
this.scroller.append(this.grid);
|
|
1139
1147
|
this.el.append(this.scroller);
|
|
1140
1148
|
this.el.classList.toggle("tnc-dg--auto", opts.height === "auto");
|
|
1141
1149
|
const RO = ctx.win.ResizeObserver;
|
|
1142
|
-
if (RO
|
|
1150
|
+
if (RO) {
|
|
1143
1151
|
this.resizeObs = new RO(() => {
|
|
1144
1152
|
if (this.sizeKey() !== this.lastHeights) this.render(null);
|
|
1145
1153
|
});
|
|
@@ -1157,6 +1165,7 @@ var DayGridView = class {
|
|
|
1157
1165
|
topPx = 24;
|
|
1158
1166
|
lastHeights = "";
|
|
1159
1167
|
dotsMode;
|
|
1168
|
+
dragPreviews = [];
|
|
1160
1169
|
daysPerWeek() {
|
|
1161
1170
|
const { days } = this.ctx;
|
|
1162
1171
|
const first = days[0];
|
|
@@ -1170,16 +1179,71 @@ var DayGridView = class {
|
|
|
1170
1179
|
}
|
|
1171
1180
|
render(_change) {
|
|
1172
1181
|
const { ctx } = this;
|
|
1182
|
+
this.clearDragPreviews();
|
|
1183
|
+
const dragged = ctx.draggingId ? ctx.store.get(ctx.draggingId) : void 0;
|
|
1184
|
+
const source = dragged ? this.elementsFor(dragged.id)[0] : void 0;
|
|
1185
|
+
if (dragged && source && this.el.closest(".tnc-dragging-move")) {
|
|
1186
|
+
const proposals = [dragged, ...[...ctx.store.all()].filter((item) => item.id !== dragged.id && item.item.preview)];
|
|
1187
|
+
for (const proposal of proposals) {
|
|
1188
|
+
const original = this.elementsFor(proposal.id)[0] ?? source;
|
|
1189
|
+
const bounds = original.getBoundingClientRect();
|
|
1190
|
+
const sourceWeek = original.closest(".tnc-dg-week");
|
|
1191
|
+
const offset = bounds.top - sourceWeek.getBoundingClientRect().top;
|
|
1192
|
+
for (const week of this.weeks) {
|
|
1193
|
+
const start = week.days.findIndex((day) => day >= proposal.startDay && day < proposal.endDay);
|
|
1194
|
+
if (start < 0) continue;
|
|
1195
|
+
let end = start + 1;
|
|
1196
|
+
while (!this.dotsMode && end < week.days.length && week.days[end] < proposal.endDay) end++;
|
|
1197
|
+
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 });
|
|
1198
|
+
preview.classList.add("tnc-month-preview");
|
|
1199
|
+
preview.inert = true;
|
|
1200
|
+
setStyle(preview, {
|
|
1201
|
+
left: `calc(${pct(start / week.days.length)} + 2px)`,
|
|
1202
|
+
width: `calc(${pct((end - start) / week.days.length)} - 4px)`,
|
|
1203
|
+
top: `${Math.max(this.topPx, Math.min(offset, week.row.clientHeight - bounds.height - 4))}px`,
|
|
1204
|
+
height: `${bounds.height}px`
|
|
1205
|
+
});
|
|
1206
|
+
week.layer.append(preview);
|
|
1207
|
+
this.dragPreviews.push(preview);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
this.dotsMode = ctx.opts.monthDisplay !== "full" || this.grid.clientWidth / this.daysPerWeek() < 72;
|
|
1213
|
+
this.el.classList.toggle("tnc-dg--dots", this.dotsMode);
|
|
1214
|
+
this.el.classList.toggle("tnc-dg--compact", this.dotsMode && ctx.opts.monthDisplay === "full");
|
|
1215
|
+
this.el.classList.toggle("tnc-dg--chips", ctx.opts.monthDisplay === "chips" && this.weeks[0].dots[0].box.clientHeight >= 44);
|
|
1173
1216
|
const cs = ctx.win.getComputedStyle(this.el);
|
|
1174
1217
|
this.barPx = px(cs, "--tnc-bar-h", 20);
|
|
1175
1218
|
this.topPx = this.weeks[0]?.cells[0]?.querySelector(".tnc-dg-top")?.offsetHeight || px(cs, "--tnc-dg-top-h", 24);
|
|
1176
1219
|
this.morePx = this.measureMore(cs);
|
|
1177
1220
|
this.lastHeights = this.sizeKey();
|
|
1178
1221
|
for (const w of this.weeks) {
|
|
1179
|
-
|
|
1180
|
-
|
|
1222
|
+
for (const cell of w.cells) {
|
|
1223
|
+
if (this.dotsMode) cell.tabIndex = 0;
|
|
1224
|
+
else cell.removeAttribute("tabindex");
|
|
1225
|
+
if (this.dotsMode && ctx.opts.monthDisplay === "full") cell.setAttribute("aria-haspopup", "dialog");
|
|
1226
|
+
else cell.removeAttribute("aria-haspopup");
|
|
1227
|
+
}
|
|
1228
|
+
for (const cell of w.dots) cell.box.hidden = !this.dotsMode;
|
|
1229
|
+
if (this.dotsMode) {
|
|
1230
|
+
w.pool.begin();
|
|
1231
|
+
w.pool.end();
|
|
1232
|
+
w.more.replaceChildren();
|
|
1233
|
+
this.renderDots(w);
|
|
1234
|
+
} else {
|
|
1235
|
+
for (const cell of w.dots) {
|
|
1236
|
+
cell.pool.begin();
|
|
1237
|
+
cell.pool.end();
|
|
1238
|
+
}
|
|
1239
|
+
this.renderWeek(w);
|
|
1240
|
+
}
|
|
1181
1241
|
}
|
|
1182
1242
|
}
|
|
1243
|
+
clearDragPreviews() {
|
|
1244
|
+
for (const preview of this.dragPreviews) this.ctx.destroyItemEl(preview);
|
|
1245
|
+
this.dragPreviews = [];
|
|
1246
|
+
}
|
|
1183
1247
|
/** Height of a "+N more" link as the host styles it (`--tnc-more-h`, or a taller min-height). */
|
|
1184
1248
|
measureMore(cs) {
|
|
1185
1249
|
const fallback = px(cs, "--tnc-more-h", this.barPx);
|
|
@@ -1276,7 +1340,8 @@ var DayGridView = class {
|
|
|
1276
1340
|
w.more.append(link);
|
|
1277
1341
|
}
|
|
1278
1342
|
existing.delete(key);
|
|
1279
|
-
link.
|
|
1343
|
+
link.setAttribute("aria-label", ctx.text.more(items.length));
|
|
1344
|
+
link.textContent = w.row.clientWidth / n < 72 ? `+${new Intl.NumberFormat(ctx.opts.locale).format(items.length)}` : ctx.text.more(items.length);
|
|
1280
1345
|
setStyle(link, {
|
|
1281
1346
|
left: `calc(${pct(col / n)} + 2px)`,
|
|
1282
1347
|
width: `calc(${pct(1 / n)} - 4px)`,
|
|
@@ -1306,10 +1371,11 @@ var DayGridView = class {
|
|
|
1306
1371
|
const { dayMaxEvents } = ctx.opts;
|
|
1307
1372
|
const first = w.dots[0].box;
|
|
1308
1373
|
const cs = ctx.win.getComputedStyle(first);
|
|
1309
|
-
const
|
|
1374
|
+
const chips = this.el.classList.contains("tnc-dg--chips");
|
|
1375
|
+
const size = chips ? px(cs, "--tnc-chip-h", 14) : px(cs, "--tnc-dot-size", 8);
|
|
1310
1376
|
const gap = px(cs, "--tnc-dot-gap", 3);
|
|
1311
1377
|
const step = size + gap;
|
|
1312
|
-
const perRow = Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
1378
|
+
const perRow = chips ? 1 : Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
1313
1379
|
const rows = Math.max(1, Math.floor((first.clientHeight + gap) / step));
|
|
1314
1380
|
const fits = dayMaxEvents === false ? Infinity : perRow * rows;
|
|
1315
1381
|
const cap = typeof dayMaxEvents === "number" && dayMaxEvents > 0 ? Math.min(dayMaxEvents, fits) : fits;
|
|
@@ -1318,7 +1384,7 @@ var DayGridView = class {
|
|
|
1318
1384
|
w.days.forEach((day, i) => {
|
|
1319
1385
|
const cell = w.dots[i];
|
|
1320
1386
|
const items = this.itemsOn(day);
|
|
1321
|
-
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(
|
|
1387
|
+
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(1, cap - (chips ? 1 : 2)) : Math.max(1, cap);
|
|
1322
1388
|
cell.pool.begin();
|
|
1323
1389
|
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 }));
|
|
1324
1390
|
cell.pool.end();
|
|
@@ -1369,6 +1435,7 @@ var DayGridView = class {
|
|
|
1369
1435
|
return this.weeks.flatMap((w) => [...w.pool.elementsFor(id), ...(w.dots ?? []).flatMap((c) => c.pool.elementsFor(id))]);
|
|
1370
1436
|
}
|
|
1371
1437
|
destroy() {
|
|
1438
|
+
this.clearDragPreviews();
|
|
1372
1439
|
this.resizeObs?.disconnect();
|
|
1373
1440
|
for (const w of this.weeks) {
|
|
1374
1441
|
w.pool.clear();
|
|
@@ -1421,6 +1488,7 @@ var ListView = class {
|
|
|
1421
1488
|
frame = null;
|
|
1422
1489
|
/** A picked day stays marked until the list is scrolled by hand. */
|
|
1423
1490
|
pinned = false;
|
|
1491
|
+
pinnedTop = 0;
|
|
1424
1492
|
itemsOn(day) {
|
|
1425
1493
|
const { store, opts } = this.ctx;
|
|
1426
1494
|
const out = store.spanningIn(day, day + 1, true);
|
|
@@ -1435,6 +1503,7 @@ var ListView = class {
|
|
|
1435
1503
|
const leadItems = [];
|
|
1436
1504
|
const inLead = /* @__PURE__ */ new Set();
|
|
1437
1505
|
const keep = /* @__PURE__ */ new Set();
|
|
1506
|
+
let itemCount = 0;
|
|
1438
1507
|
let prev = null;
|
|
1439
1508
|
for (const day of days) {
|
|
1440
1509
|
let items = this.itemsOn(day);
|
|
@@ -1448,7 +1517,7 @@ var ListView = class {
|
|
|
1448
1517
|
return false;
|
|
1449
1518
|
});
|
|
1450
1519
|
}
|
|
1451
|
-
if (!items.length && !agenda) continue;
|
|
1520
|
+
if (!items.length && !agenda && opts.listDayCount !== 1) continue;
|
|
1452
1521
|
keep.add(day);
|
|
1453
1522
|
let section = this.sections.get(day);
|
|
1454
1523
|
let pool = this.pools.get(day);
|
|
@@ -1467,6 +1536,7 @@ var ListView = class {
|
|
|
1467
1536
|
this.pools.set(day, pool);
|
|
1468
1537
|
}
|
|
1469
1538
|
section.classList.toggle("is-empty", !items.length);
|
|
1539
|
+
itemCount += items.length;
|
|
1470
1540
|
this.fill(pool, items, () => day);
|
|
1471
1541
|
if (prev ? prev.nextSibling !== section : this.body.firstChild !== section) {
|
|
1472
1542
|
if (prev) prev.after(section);
|
|
@@ -1482,13 +1552,14 @@ var ListView = class {
|
|
|
1482
1552
|
this.sections.delete(day);
|
|
1483
1553
|
}
|
|
1484
1554
|
this.renderLead(leadItems);
|
|
1485
|
-
let empty = this.body.querySelector("
|
|
1486
|
-
if (!
|
|
1555
|
+
let empty = this.body.querySelector(".tnc-list-empty");
|
|
1556
|
+
if (!itemCount && !leadItems.length && (!agenda || opts.listEmptyContent)) {
|
|
1487
1557
|
if (!empty) {
|
|
1488
1558
|
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
1489
1559
|
const content = ctx.opts.listEmptyContent?.();
|
|
1490
1560
|
if (content) empty.replaceChildren(content);
|
|
1491
|
-
this.
|
|
1561
|
+
if (this.sections.size === 1) this.sections.values().next().value.append(empty);
|
|
1562
|
+
else this.body.prepend(empty);
|
|
1492
1563
|
}
|
|
1493
1564
|
} else empty?.remove();
|
|
1494
1565
|
this.ctx.strip?.draw();
|
|
@@ -1546,9 +1617,10 @@ var ListView = class {
|
|
|
1546
1617
|
const section = day === this.ctx.days[0] && this.lead ? this.lead.section : this.sections.get(day);
|
|
1547
1618
|
if (!section) return;
|
|
1548
1619
|
this.ctx.strip?.measure();
|
|
1549
|
-
section.
|
|
1620
|
+
this.scroller.scrollTop += section.getBoundingClientRect().top - this.scroller.getBoundingClientRect().top;
|
|
1550
1621
|
this.ctx.strip?.setFocus(day);
|
|
1551
1622
|
this.pinned = true;
|
|
1623
|
+
this.pinnedTop = this.scroller.scrollTop;
|
|
1552
1624
|
}
|
|
1553
1625
|
fold = () => {
|
|
1554
1626
|
this.pinned = false;
|
|
@@ -1556,6 +1628,7 @@ var ListView = class {
|
|
|
1556
1628
|
if (strip?.expanded) strip.setExpanded(false);
|
|
1557
1629
|
};
|
|
1558
1630
|
onScroll = () => {
|
|
1631
|
+
if (this.pinned && Math.abs(this.scroller.scrollTop - this.pinnedTop) > 1) this.pinned = false;
|
|
1559
1632
|
if (this.frame !== null) return;
|
|
1560
1633
|
this.frame = this.ctx.win.requestAnimationFrame(() => {
|
|
1561
1634
|
this.frame = null;
|
|
@@ -1566,7 +1639,7 @@ var ListView = class {
|
|
|
1566
1639
|
followTop() {
|
|
1567
1640
|
const strip = this.ctx.strip;
|
|
1568
1641
|
if (!strip || this.pinned) return;
|
|
1569
|
-
const edge = strip.el.getBoundingClientRect().bottom + 1;
|
|
1642
|
+
const edge = Math.max(strip.el.getBoundingClientRect().bottom, this.scroller.getBoundingClientRect().top) + 1;
|
|
1570
1643
|
for (const day of this.ctx.days) {
|
|
1571
1644
|
const section = this.sections.get(day);
|
|
1572
1645
|
if (section && section.getBoundingClientRect().bottom > edge) {
|
|
@@ -1614,10 +1687,11 @@ var ListView = class {
|
|
|
1614
1687
|
// src/dom/views/strip.ts
|
|
1615
1688
|
var SWIPE_PX = 40;
|
|
1616
1689
|
var DateStrip = class {
|
|
1617
|
-
constructor(ctx, pick, focus, onShown) {
|
|
1690
|
+
constructor(ctx, pick, focus, onShown, onPage) {
|
|
1618
1691
|
this.ctx = ctx;
|
|
1619
1692
|
this.pick = pick;
|
|
1620
1693
|
this.onShown = onShown;
|
|
1694
|
+
this.onPage = onPage;
|
|
1621
1695
|
const { doc, text } = ctx;
|
|
1622
1696
|
this.focus = focus;
|
|
1623
1697
|
this.shown = startOfWeek(focus, ctx.firstDay);
|
|
@@ -1635,13 +1709,16 @@ var DateStrip = class {
|
|
|
1635
1709
|
head.append(this.title, this.todayBtn, prev, next, this.slot);
|
|
1636
1710
|
this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
|
|
1637
1711
|
this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
|
|
1638
|
-
const handle = h(doc, "
|
|
1639
|
-
handle.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
1712
|
+
const handle = h(doc, "div", "tnc-strip-handle", { "aria-hidden": "true" });
|
|
1640
1713
|
this.el.append(head, this.dows, this.grid, handle);
|
|
1641
1714
|
this.grid.addEventListener("click", this.onClick);
|
|
1642
1715
|
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
1643
1716
|
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
1644
1717
|
this.el.addEventListener("pointerup", this.onPointerUp);
|
|
1718
|
+
this.el.addEventListener("touchstart", this.ownTouch, { passive: true });
|
|
1719
|
+
this.el.addEventListener("touchmove", this.ownTouch, { passive: true });
|
|
1720
|
+
this.el.addEventListener("touchend", this.ownTouch, { passive: true });
|
|
1721
|
+
this.el.addEventListener("touchcancel", this.ownTouch, { passive: true });
|
|
1645
1722
|
this.el.addEventListener("pointercancel", this.onPointerCancel);
|
|
1646
1723
|
this.el.addEventListener("click", this.onClickCapture, true);
|
|
1647
1724
|
this.draw();
|
|
@@ -1650,6 +1727,7 @@ var DateStrip = class {
|
|
|
1650
1727
|
ctx;
|
|
1651
1728
|
pick;
|
|
1652
1729
|
onShown;
|
|
1730
|
+
onPage;
|
|
1653
1731
|
el;
|
|
1654
1732
|
title;
|
|
1655
1733
|
dows;
|
|
@@ -1708,6 +1786,7 @@ var DateStrip = class {
|
|
|
1708
1786
|
step(dir) {
|
|
1709
1787
|
this.shown = this.expanded ? addMonths(this.shown, dir) : this.shown + 7 * dir;
|
|
1710
1788
|
this.draw();
|
|
1789
|
+
this.onPage?.(this.expanded ? this.shown : this.focus + 7 * dir);
|
|
1711
1790
|
}
|
|
1712
1791
|
days() {
|
|
1713
1792
|
if (!this.expanded) return Array.from({ length: 7 }, (_, i) => this.shown + i);
|
|
@@ -1732,7 +1811,8 @@ var DateStrip = class {
|
|
|
1732
1811
|
const tabDay = days.includes(this.focus) ? this.focus : days[0];
|
|
1733
1812
|
const cells = days.map((d) => {
|
|
1734
1813
|
const arg = ctx.dayArg(d);
|
|
1735
|
-
const
|
|
1814
|
+
const items = store.inRange(d, d + 1).filter((n) => n.item.display !== "background" && n.item.display !== "none");
|
|
1815
|
+
const count = items.length;
|
|
1736
1816
|
const first = ymdOfDay(d).day === 1 && d !== days[0] && !this.expanded;
|
|
1737
1817
|
const label = fmt.longDate(d);
|
|
1738
1818
|
const cell = h(
|
|
@@ -1756,7 +1836,15 @@ var DateStrip = class {
|
|
|
1756
1836
|
}
|
|
1757
1837
|
);
|
|
1758
1838
|
if (first) cell.append(h(doc, "span", "tnc-strip-month", { "aria-hidden": "true" }, fmt.monthShort(d)));
|
|
1759
|
-
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d))
|
|
1839
|
+
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d)));
|
|
1840
|
+
const dots = h(doc, "span", "tnc-strip-dots", { "aria-hidden": "true" });
|
|
1841
|
+
for (const item of items.slice(0, 3)) {
|
|
1842
|
+
const dot = h(doc, "span", "tnc-strip-dot");
|
|
1843
|
+
const color = item.item.color || item.item.borderColor || item.item.backgroundColor;
|
|
1844
|
+
if (color) dot.style.backgroundColor = color;
|
|
1845
|
+
dots.append(dot);
|
|
1846
|
+
}
|
|
1847
|
+
cell.append(dots);
|
|
1760
1848
|
return cell;
|
|
1761
1849
|
});
|
|
1762
1850
|
this.grid.replaceChildren(...cells);
|
|
@@ -1796,6 +1884,9 @@ var DateStrip = class {
|
|
|
1796
1884
|
if (ev.button !== 0 || this.ctx.draggingId) return;
|
|
1797
1885
|
this.swipe = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
|
|
1798
1886
|
};
|
|
1887
|
+
ownTouch = (event) => {
|
|
1888
|
+
event.stopPropagation();
|
|
1889
|
+
};
|
|
1799
1890
|
onPointerUp = (ev) => {
|
|
1800
1891
|
const s = this.swipe;
|
|
1801
1892
|
this.swipe = null;
|
|
@@ -1835,6 +1926,7 @@ var TimeGridView = class {
|
|
|
1835
1926
|
const showGrid = opts.timeGrid;
|
|
1836
1927
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1837
1928
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1929
|
+
this.el.classList.toggle("tnc-tg--pinch", showGrid && opts.pinchZoom);
|
|
1838
1930
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1839
1931
|
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1840
1932
|
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
@@ -1984,6 +2076,11 @@ var TimeGridView = class {
|
|
|
1984
2076
|
}
|
|
1985
2077
|
if (this.nowGutter) gutter.append(this.nowGutter);
|
|
1986
2078
|
}
|
|
2079
|
+
/** Slot height as drawn, which `expandRows` can stretch past the option. */
|
|
2080
|
+
get drawnSlotHeight() {
|
|
2081
|
+
return this.ctx.slot.duration * this.ppm;
|
|
2082
|
+
}
|
|
2083
|
+
/** Applies `slotHeight`. Items are placed in percentages, so they follow without a render. */
|
|
1987
2084
|
applyScale() {
|
|
1988
2085
|
const { opts, slot } = this.ctx;
|
|
1989
2086
|
this.ppm = opts.slotHeight / slot.duration;
|
|
@@ -2164,15 +2261,17 @@ var TimeGridView = class {
|
|
|
2164
2261
|
const { opts } = ctx;
|
|
2165
2262
|
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
2166
2263
|
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
2167
|
-
const
|
|
2264
|
+
const overlapThreshold = opts.overlapStackThreshold ?? 0;
|
|
2265
|
+
const collapsedClusters = new Set(placements.filter((placement) => {
|
|
2168
2266
|
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2267
|
+
const denseOverlap = overlapThreshold > 1 && placement.cols >= overlapThreshold;
|
|
2268
|
+
return columns > 1 && (columns * readableWidth > laneWidth || denseOverlap);
|
|
2269
|
+
}).map((placement) => placement.cluster));
|
|
2173
2270
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
2174
2271
|
pool.begin();
|
|
2175
2272
|
for (const p of placements) {
|
|
2273
|
+
const withMore = lane === "plan" || collapsedClusters.has(p.cluster);
|
|
2274
|
+
const maxStack = collapsedClusters.has(p.cluster) ? 1 : opts.eventMaxStack;
|
|
2176
2275
|
const preview = p.item.item.preview || p.item.id === ctx.draggingId;
|
|
2177
2276
|
if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
|
|
2178
2277
|
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
|
|
@@ -2185,7 +2284,7 @@ var TimeGridView = class {
|
|
|
2185
2284
|
}
|
|
2186
2285
|
const cols = !preview && withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
|
|
2187
2286
|
const span = Math.min(p.span, cols - p.col);
|
|
2188
|
-
const left = p.col / cols;
|
|
2287
|
+
const left = p.item.id === ctx.draggingId ? 0 : p.col / cols;
|
|
2189
2288
|
let width = span / cols;
|
|
2190
2289
|
if (opts.slotEventOverlap && cols > 1 && p.col + span < cols) width = Math.min(1 - left, width * 1.7);
|
|
2191
2290
|
const el = pool.place(p.item.id, this.meta(p, false));
|
|
@@ -2200,8 +2299,9 @@ var TimeGridView = class {
|
|
|
2200
2299
|
});
|
|
2201
2300
|
}
|
|
2202
2301
|
pool.end();
|
|
2203
|
-
if (!withMore) return;
|
|
2204
2302
|
for (const [cluster, rec] of hiddenByCluster) {
|
|
2303
|
+
const collapse = collapsedClusters.has(cluster);
|
|
2304
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2205
2305
|
const key = `${lane}-${cluster}`;
|
|
2206
2306
|
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
2207
2307
|
if (!link) {
|
|
@@ -2215,8 +2315,15 @@ var TimeGridView = class {
|
|
|
2215
2315
|
link.classList.toggle("tnc-more--collision", collapse);
|
|
2216
2316
|
link.textContent = `+${rec.count}`;
|
|
2217
2317
|
if (collapse) {
|
|
2218
|
-
|
|
2219
|
-
|
|
2318
|
+
const items = placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.item.title);
|
|
2319
|
+
const count = ctx.text.appointments(rec.count);
|
|
2320
|
+
const interval = `${ctx.fmt.time(rec.start)} \u2013 ${ctx.fmt.time(rec.end)}`;
|
|
2321
|
+
link.replaceChildren(
|
|
2322
|
+
h(ctx.doc, "span", "tnc-collision-count", null, String(rec.count)),
|
|
2323
|
+
h(ctx.doc, "span", "tnc-collision-range", null, interval),
|
|
2324
|
+
h(ctx.doc, "span", "tnc-collision-title", { "aria-hidden": "true" }, items.join(" \xB7 "))
|
|
2325
|
+
);
|
|
2326
|
+
link.setAttribute("aria-label", `${count}, ${interval}: ${items.join(", ")}`);
|
|
2220
2327
|
link.title = link.getAttribute("aria-label");
|
|
2221
2328
|
} else {
|
|
2222
2329
|
link.removeAttribute("aria-label");
|
|
@@ -2225,6 +2332,7 @@ var TimeGridView = class {
|
|
|
2225
2332
|
const cols = maxStack;
|
|
2226
2333
|
setStyle(link, {
|
|
2227
2334
|
top: top(rec.start),
|
|
2335
|
+
height: collapse ? pct((rec.end - rec.start) / this.minutes) : "",
|
|
2228
2336
|
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
2229
2337
|
});
|
|
2230
2338
|
}
|
|
@@ -2291,12 +2399,10 @@ var TimeGridView = class {
|
|
|
2291
2399
|
const r2 = cells[i2].getBoundingClientRect();
|
|
2292
2400
|
if (x >= r2.left && x < r2.right) return i2;
|
|
2293
2401
|
}
|
|
2294
|
-
const firstR = cells[0]?.getBoundingClientRect();
|
|
2295
|
-
const lastR = cells[cells.length - 1]?.getBoundingClientRect();
|
|
2296
|
-
if (firstR && x < firstR.left) return 0;
|
|
2297
|
-
if (lastR && x >= lastR.right) return cells.length - 1;
|
|
2298
2402
|
return -1;
|
|
2299
2403
|
};
|
|
2404
|
+
const bounds = this.scroller.getBoundingClientRect();
|
|
2405
|
+
if (x < bounds.left || x >= bounds.right || y < bounds.top || y >= bounds.bottom) return null;
|
|
2300
2406
|
if (this.allDayRow) {
|
|
2301
2407
|
const r2 = this.allDayRow.getBoundingClientRect();
|
|
2302
2408
|
if (y >= r2.top && y < r2.bottom || !this.body && y >= r2.top) {
|
|
@@ -2361,7 +2467,9 @@ var TimeGridView = class {
|
|
|
2361
2467
|
scrollToTime(minute) {
|
|
2362
2468
|
if (!this.body) return;
|
|
2363
2469
|
const y = (minute - this.ctx.slot.min) * this.ppm;
|
|
2364
|
-
|
|
2470
|
+
const label = this.gutter?.querySelector(".tnc-slot-label");
|
|
2471
|
+
const inset = label ? Math.ceil(label.getBoundingClientRect().height / 2) + 2 : 0;
|
|
2472
|
+
this.scroller.scrollTop = Math.max(0, y - inset);
|
|
2365
2473
|
}
|
|
2366
2474
|
getScroll() {
|
|
2367
2475
|
return { top: this.scroller.scrollTop, left: this.scroller.scrollLeft };
|
|
@@ -3019,6 +3127,14 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
3019
3127
|
setScroll(s) {
|
|
3020
3128
|
this.scroller.scrollTop = s.top;
|
|
3021
3129
|
}
|
|
3130
|
+
revealDay(day) {
|
|
3131
|
+
const month = this.cells.find((cell) => cell.day === day)?.el.closest(".tnc-ym");
|
|
3132
|
+
if (!month) return;
|
|
3133
|
+
const bounds = month.getBoundingClientRect();
|
|
3134
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
3135
|
+
if (bounds.top < viewport.top) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
3136
|
+
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
3137
|
+
}
|
|
3022
3138
|
elementsFor(_id) {
|
|
3023
3139
|
return [];
|
|
3024
3140
|
}
|
|
@@ -3031,6 +3147,8 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
3031
3147
|
var EXTERNAL_ID = "__tnc_external__";
|
|
3032
3148
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
3033
3149
|
var MAX_AGENDA_DAYS = 366;
|
|
3150
|
+
var PINCH_MIN_SLOT = 16;
|
|
3151
|
+
var PINCH_MAX_SLOT = 96;
|
|
3034
3152
|
function sameOption(a, b, depth = 0) {
|
|
3035
3153
|
if (a === b) return true;
|
|
3036
3154
|
if (typeof a === "function" && typeof b === "function") return true;
|
|
@@ -3058,7 +3176,7 @@ var Calendar = class {
|
|
|
3058
3176
|
this.win = this.doc.defaultView ?? globalThis;
|
|
3059
3177
|
this.opts = { ...defaultOptions(), ...options };
|
|
3060
3178
|
this.zone = getTimeZone(this.opts.timeZone);
|
|
3061
|
-
this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
3179
|
+
this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
3062
3180
|
this.text = this.resolveText();
|
|
3063
3181
|
this.order = parseOrder(this.opts.eventOrder);
|
|
3064
3182
|
this.store = new ItemStore(this.zone, {
|
|
@@ -3098,10 +3216,18 @@ var Calendar = class {
|
|
|
3098
3216
|
conflicts = /* @__PURE__ */ new Map();
|
|
3099
3217
|
slot = { min: 0, max: MINUTES_PER_DAY, duration: 30, snap: 30, scroll: 480 };
|
|
3100
3218
|
draggingId = null;
|
|
3219
|
+
listDragPreview = null;
|
|
3220
|
+
dragSources = [];
|
|
3221
|
+
dragWidth = null;
|
|
3222
|
+
dragScrollFrame = null;
|
|
3223
|
+
dragScrollOrigins = /* @__PURE__ */ new Map();
|
|
3224
|
+
dragScrollEdges = /* @__PURE__ */ new Map();
|
|
3225
|
+
itemWriteStates = /* @__PURE__ */ new Map();
|
|
3101
3226
|
dragRow = null;
|
|
3102
3227
|
strip = null;
|
|
3103
3228
|
stripSize = null;
|
|
3104
3229
|
rebuilding = false;
|
|
3230
|
+
pinch = null;
|
|
3105
3231
|
toolbarEl;
|
|
3106
3232
|
viewHost;
|
|
3107
3233
|
current = null;
|
|
@@ -3111,6 +3237,7 @@ var Calendar = class {
|
|
|
3111
3237
|
session = null;
|
|
3112
3238
|
selection = null;
|
|
3113
3239
|
suppressClick = false;
|
|
3240
|
+
cancelledPointerId = null;
|
|
3114
3241
|
selectedIds = /* @__PURE__ */ new Set();
|
|
3115
3242
|
popover = null;
|
|
3116
3243
|
timer = null;
|
|
@@ -3128,6 +3255,7 @@ var Calendar = class {
|
|
|
3128
3255
|
this.positionMore();
|
|
3129
3256
|
});
|
|
3130
3257
|
this.resizeObs.observe(this.root);
|
|
3258
|
+
this.resizeObs.observe(this.toolbarEl);
|
|
3131
3259
|
}
|
|
3132
3260
|
}
|
|
3133
3261
|
onAdoptCheck = () => {
|
|
@@ -3208,7 +3336,7 @@ var Calendar = class {
|
|
|
3208
3336
|
defaultAllDayDays: this.opts.defaultAllDayDays
|
|
3209
3337
|
});
|
|
3210
3338
|
}
|
|
3211
|
-
if (has("locale") || has("hour12")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
3339
|
+
if (has("locale") || has("hour12") || has("formatTime")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
3212
3340
|
if (has("text") || has("locale")) this.text = this.resolveText();
|
|
3213
3341
|
if (has("eventOrder")) this.order = parseOrder(this.opts.eventOrder);
|
|
3214
3342
|
if (has("date")) this.anchor = this.toDay(this.opts.date) ?? this.anchor;
|
|
@@ -3254,7 +3382,12 @@ var Calendar = class {
|
|
|
3254
3382
|
gotoDate(date) {
|
|
3255
3383
|
const day = this.toDay(date);
|
|
3256
3384
|
if (day === null) return;
|
|
3385
|
+
const sameMonth = this.opts.view === "month" && startOfMonth(day) === startOfMonth(this.anchor);
|
|
3257
3386
|
this.anchor = day;
|
|
3387
|
+
if (sameMonth) {
|
|
3388
|
+
this.view = { ...this.view, anchor: day };
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3258
3391
|
this.rebuild(true);
|
|
3259
3392
|
}
|
|
3260
3393
|
prev() {
|
|
@@ -3400,7 +3533,8 @@ var Calendar = class {
|
|
|
3400
3533
|
out.push(...classList(this.opts.dayClassNames?.(this.dayArg(day))));
|
|
3401
3534
|
return out;
|
|
3402
3535
|
}
|
|
3403
|
-
isEditable(n, what) {
|
|
3536
|
+
isEditable(n, what, presentation = false) {
|
|
3537
|
+
if (!presentation && this.itemWriteStates.get(n.id) === "pending") return false;
|
|
3404
3538
|
if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
|
|
3405
3539
|
if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
|
|
3406
3540
|
const it = n.item;
|
|
@@ -3440,13 +3574,17 @@ var Calendar = class {
|
|
|
3440
3574
|
if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
|
|
3441
3575
|
if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
|
|
3442
3576
|
const indicator = variant === "indicator";
|
|
3443
|
-
if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
|
|
3444
|
-
if (this.draggingId === n.id)
|
|
3577
|
+
if (!preview && !indicator && (this.isEditable(n, "start", true) || this.isEditable(n, "duration", true))) el.classList.add("is-draggable");
|
|
3578
|
+
if (this.draggingId === n.id) {
|
|
3579
|
+
el.classList.add("is-dragging");
|
|
3580
|
+
if (variant === "timed" && this.dragWidth !== null) el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
3581
|
+
}
|
|
3445
3582
|
if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
|
|
3446
3583
|
el.classList.add(...classList(it.className));
|
|
3447
3584
|
const arg = this.itemArg(el, meta);
|
|
3448
3585
|
el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
|
|
3449
3586
|
el.dataset.id = n.id;
|
|
3587
|
+
this.applyItemWriteState(el, n.id);
|
|
3450
3588
|
if (it.kind) el.dataset.kind = it.kind;
|
|
3451
3589
|
const color = it.color ?? it.borderColor;
|
|
3452
3590
|
if (color) el.style.setProperty("--tnc-c", color);
|
|
@@ -3463,6 +3601,7 @@ var Calendar = class {
|
|
|
3463
3601
|
const custom = variant === "list" ? this.opts.listItemContent?.(arg) : null;
|
|
3464
3602
|
const content = indicator ? null : custom ?? this.opts.itemContent?.(arg);
|
|
3465
3603
|
if (indicator) {
|
|
3604
|
+
if (this.opts.monthDisplay === "chips") el.append(h(this.doc, "span", "tnc-item-title", null, it.title));
|
|
3466
3605
|
} else if (content != null) {
|
|
3467
3606
|
const wrap = h(this.doc, "div", "tnc-item-main");
|
|
3468
3607
|
wrap.append(content);
|
|
@@ -3475,6 +3614,7 @@ var Calendar = class {
|
|
|
3475
3614
|
return el;
|
|
3476
3615
|
}
|
|
3477
3616
|
refreshItemEl(el, meta) {
|
|
3617
|
+
this.applyItemWriteState(el, meta.n.id);
|
|
3478
3618
|
const time = el.querySelector(":scope .tnc-item-time");
|
|
3479
3619
|
if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
|
|
3480
3620
|
el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
|
|
@@ -3657,6 +3797,7 @@ var Calendar = class {
|
|
|
3657
3797
|
this.current.scrollToTime(this.slot.scroll);
|
|
3658
3798
|
this.current.revealDay(this.anchor);
|
|
3659
3799
|
} else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
3800
|
+
else if (this.current instanceof YearView) this.current.revealDay(this.anchor);
|
|
3660
3801
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
3661
3802
|
}
|
|
3662
3803
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -3682,9 +3823,11 @@ var Calendar = class {
|
|
|
3682
3823
|
}
|
|
3683
3824
|
/** The range, run on to the end of the strip's days so their dots are right. */
|
|
3684
3825
|
dataRangeFor(range) {
|
|
3685
|
-
const
|
|
3686
|
-
|
|
3687
|
-
|
|
3826
|
+
const window = this.strip?.window();
|
|
3827
|
+
const start = window ? Math.min(range.startDay, window.start) : range.startDay;
|
|
3828
|
+
const end = window ? Math.max(range.endDay, window.end) : range.endDay;
|
|
3829
|
+
if (start === range.startDay && end === range.endDay) return range;
|
|
3830
|
+
return { ...range, startDay: start, start: new Date(toInstant(start, 0, this.zone)), endDay: end, end: new Date(toInstant(end, 0, this.zone)) };
|
|
3688
3831
|
}
|
|
3689
3832
|
/** Adds, updates or removes the date strip for the current view. */
|
|
3690
3833
|
syncStrip(navigated) {
|
|
@@ -3703,7 +3846,9 @@ var Calendar = class {
|
|
|
3703
3846
|
this.root.classList.add("tnc--has-strip");
|
|
3704
3847
|
const { startDay, endDay } = this.view.range;
|
|
3705
3848
|
if (!this.strip) {
|
|
3706
|
-
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown())
|
|
3849
|
+
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown(), (day) => {
|
|
3850
|
+
if (this.agendaList()) this.showListDay(day);
|
|
3851
|
+
});
|
|
3707
3852
|
this.strip = strip;
|
|
3708
3853
|
this.root.insertBefore(strip.el, this.viewHost);
|
|
3709
3854
|
const RO = this.win.ResizeObserver;
|
|
@@ -3731,20 +3876,24 @@ var Calendar = class {
|
|
|
3731
3876
|
stripShown() {
|
|
3732
3877
|
if (!this.strip || this.rebuilding || !this.view) return;
|
|
3733
3878
|
const next = this.dataRangeFor(this.view.range);
|
|
3734
|
-
if (next.endDay === this.view.dataRange.endDay) return;
|
|
3879
|
+
if (next.startDay === this.view.dataRange.startDay && next.endDay === this.view.dataRange.endDay) return;
|
|
3735
3880
|
this.view = { ...this.view, dataRange: next };
|
|
3736
3881
|
this.opts.onRangeChange?.(this.view);
|
|
3737
3882
|
}
|
|
3738
3883
|
/** What is under a point, the date strip included. */
|
|
3739
3884
|
hitAt(x, y) {
|
|
3885
|
+
const target = this.doc.elementFromPoint(x, y);
|
|
3886
|
+
if (!target || !this.root.contains(target)) {
|
|
3887
|
+
this.strip?.markTarget(null);
|
|
3888
|
+
return null;
|
|
3889
|
+
}
|
|
3740
3890
|
const strip = this.strip;
|
|
3741
3891
|
if (strip) {
|
|
3742
|
-
const
|
|
3743
|
-
const day = el ? strip.dayAt(el) : null;
|
|
3892
|
+
const day = strip.dayAt(target);
|
|
3744
3893
|
strip.markTarget(day !== null && this.draggingId ? day : null);
|
|
3745
3894
|
if (day !== null) return { kind: "day", day };
|
|
3746
3895
|
}
|
|
3747
|
-
return this.current?.hit(x, y)
|
|
3896
|
+
return this.current?.el.contains(target) ? this.current.hit(x, y) : null;
|
|
3748
3897
|
}
|
|
3749
3898
|
agendaList() {
|
|
3750
3899
|
return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
|
|
@@ -3858,11 +4007,13 @@ var Calendar = class {
|
|
|
3858
4007
|
buttons.style.display = "flex";
|
|
3859
4008
|
select.style.display = "none";
|
|
3860
4009
|
const startWidth = Array.from(start.children).reduce((width, child) => width + child.scrollWidth, 0);
|
|
3861
|
-
const
|
|
4010
|
+
const availableWidth = Math.min(this.root.clientWidth, this.toolbarEl.clientWidth);
|
|
4011
|
+
const compact = mode === "select" || mode === "auto" && startWidth + buttons.scrollWidth + 32 > availableWidth;
|
|
3862
4012
|
buttons.hidden = compact;
|
|
3863
4013
|
select.hidden = !compact;
|
|
3864
4014
|
buttons.style.display = compact ? "none" : "flex";
|
|
3865
4015
|
select.style.display = compact ? "" : "none";
|
|
4016
|
+
select.parentElement?.classList.toggle("tnc-toolbar-end--compact", compact);
|
|
3866
4017
|
}
|
|
3867
4018
|
applyChange(change) {
|
|
3868
4019
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3927,7 +4078,7 @@ var Calendar = class {
|
|
|
3927
4078
|
const s = wallTime(n.startMs, this.zone);
|
|
3928
4079
|
const start = this.fmt.time(s.minute, true);
|
|
3929
4080
|
if (variant === "dot" || variant === "bar" || variant === "indicator") return { start: s.day === day ? start : "", end: "" };
|
|
3930
|
-
if (n.startMs === n.endMs) return { start, end: "" };
|
|
4081
|
+
if (n.startMs === n.endMs || n.item.timeDisplay === "start") return { start, end: "" };
|
|
3931
4082
|
return { start, end: this.fmt.time(wallTime(n.endMs, this.zone).minute, true) };
|
|
3932
4083
|
}
|
|
3933
4084
|
fillTime(el, n, variant, day) {
|
|
@@ -3943,7 +4094,8 @@ var Calendar = class {
|
|
|
3943
4094
|
}
|
|
3944
4095
|
const s = wallTime(n.startMs, this.zone);
|
|
3945
4096
|
const e = wallTime(n.endMs, this.zone);
|
|
3946
|
-
|
|
4097
|
+
const start = `${n.item.title}, ${this.fmt.longDate(s.day)} ${this.fmt.time(s.minute)}`;
|
|
4098
|
+
return n.item.timeDisplay === "start" || n.startMs === n.endMs ? start : `${start} \u2013 ${this.fmt.time(e.minute)}`;
|
|
3947
4099
|
}
|
|
3948
4100
|
defaultContent(meta) {
|
|
3949
4101
|
const { n, variant, day } = meta;
|
|
@@ -3971,7 +4123,7 @@ var Calendar = class {
|
|
|
3971
4123
|
addHandles(el, meta) {
|
|
3972
4124
|
const { n, variant } = meta;
|
|
3973
4125
|
const ports = variant === "timeline" && !meta.mirror && n.id !== EXTERNAL_ID && !!this.opts.timeline?.onLink;
|
|
3974
|
-
if (variant === "dot" || !this.isEditable(n, "duration")) {
|
|
4126
|
+
if (variant === "dot" || !this.isEditable(n, "duration", true)) {
|
|
3975
4127
|
if (ports) this.addPorts(el, n);
|
|
3976
4128
|
return;
|
|
3977
4129
|
}
|
|
@@ -4060,7 +4212,10 @@ var Calendar = class {
|
|
|
4060
4212
|
r.addEventListener("dragover", this.onNativeDragOver);
|
|
4061
4213
|
r.addEventListener("dragleave", this.onNativeDragLeave);
|
|
4062
4214
|
r.addEventListener("drop", this.onNativeDrop);
|
|
4215
|
+
r.addEventListener("touchstart", this.onTouchStart, { passive: true });
|
|
4063
4216
|
r.addEventListener("touchmove", this.onTouchMove, { passive: false });
|
|
4217
|
+
r.addEventListener("touchend", this.onTouchEnd);
|
|
4218
|
+
r.addEventListener("touchcancel", this.onTouchEnd);
|
|
4064
4219
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
4065
4220
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
4066
4221
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -4079,7 +4234,10 @@ var Calendar = class {
|
|
|
4079
4234
|
r.removeEventListener("dragover", this.onNativeDragOver);
|
|
4080
4235
|
r.removeEventListener("dragleave", this.onNativeDragLeave);
|
|
4081
4236
|
r.removeEventListener("drop", this.onNativeDrop);
|
|
4237
|
+
r.removeEventListener("touchstart", this.onTouchStart);
|
|
4082
4238
|
r.removeEventListener("touchmove", this.onTouchMove);
|
|
4239
|
+
r.removeEventListener("touchend", this.onTouchEnd);
|
|
4240
|
+
r.removeEventListener("touchcancel", this.onTouchEnd);
|
|
4083
4241
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
4084
4242
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
4085
4243
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -4099,6 +4257,10 @@ var Calendar = class {
|
|
|
4099
4257
|
return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
|
|
4100
4258
|
}
|
|
4101
4259
|
onDocPointerDown = (ev) => {
|
|
4260
|
+
if (this.cancelledPointerId !== null) {
|
|
4261
|
+
this.cancelledPointerId = null;
|
|
4262
|
+
this.listenDoc(false);
|
|
4263
|
+
}
|
|
4102
4264
|
const t = ev.target;
|
|
4103
4265
|
if (this.popover && !this.popover.el.contains(t) && !this.popover.anchor.contains(t)) this.closeMore();
|
|
4104
4266
|
if (this.selection && !this.root.contains(t)) this.unselect();
|
|
@@ -4106,6 +4268,7 @@ var Calendar = class {
|
|
|
4106
4268
|
onPointerDown = (ev) => {
|
|
4107
4269
|
if (ev.button !== 0 || this.session) return;
|
|
4108
4270
|
const target = ev.target;
|
|
4271
|
+
if (target.closest(".tnc-dg--compact .tnc-dg-day")) return;
|
|
4109
4272
|
if (target.closest("button, a, input, select, textarea, .tnc-toolbar, .tnc-port, [data-tnc-no-drag]")) {
|
|
4110
4273
|
if (!target.closest(".tnc-item")) return;
|
|
4111
4274
|
if (target.closest(".tnc-port, [data-tnc-no-drag], input, select, textarea")) return;
|
|
@@ -4138,7 +4301,7 @@ var Calendar = class {
|
|
|
4138
4301
|
y0: ev.clientY,
|
|
4139
4302
|
active: false,
|
|
4140
4303
|
origin,
|
|
4141
|
-
grab: overflowOrigin
|
|
4304
|
+
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,
|
|
4142
4305
|
proposal: null,
|
|
4143
4306
|
denied: false,
|
|
4144
4307
|
timer: null,
|
|
@@ -4163,20 +4326,159 @@ var Calendar = class {
|
|
|
4163
4326
|
};
|
|
4164
4327
|
activate(s) {
|
|
4165
4328
|
s.active = true;
|
|
4329
|
+
if (s.lastEvent) this.leaveHover(s.lastEvent);
|
|
4166
4330
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
4167
4331
|
s.timer = null;
|
|
4168
4332
|
if (s.origin) {
|
|
4169
4333
|
this.draggingId = s.origin.id;
|
|
4170
|
-
for (const el of this.elementsFor(s.origin.id))
|
|
4334
|
+
for (const el of this.elementsFor(s.origin.id)) {
|
|
4335
|
+
if (this.meta.get(el)?.variant === "timed" && s.mode === "move") {
|
|
4336
|
+
this.dragWidth = el.getBoundingClientRect().width;
|
|
4337
|
+
el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
4338
|
+
}
|
|
4339
|
+
if (this.opts.view !== "list" && !(this.current instanceof DayGridView) && s.mode === "move") {
|
|
4340
|
+
const source = el.cloneNode(true);
|
|
4341
|
+
source.classList.remove("is-selected");
|
|
4342
|
+
source.classList.add("tnc-drag-source");
|
|
4343
|
+
source.setAttribute("aria-hidden", "true");
|
|
4344
|
+
source.inert = true;
|
|
4345
|
+
for (const node of [source, ...source.querySelectorAll("[id], [data-id], [data-task-path]")]) {
|
|
4346
|
+
node.removeAttribute("id");
|
|
4347
|
+
node.removeAttribute("data-id");
|
|
4348
|
+
node.removeAttribute("data-task-path");
|
|
4349
|
+
}
|
|
4350
|
+
el.before(source);
|
|
4351
|
+
this.dragSources.push(source);
|
|
4352
|
+
}
|
|
4353
|
+
if (this.opts.view === "list" && !this.listDragPreview && s.mode === "move") {
|
|
4354
|
+
const bounds = el.getBoundingClientRect();
|
|
4355
|
+
const preview = el.cloneNode(true);
|
|
4356
|
+
preview.classList.add("tnc-drag-preview", "is-dragging");
|
|
4357
|
+
preview.removeAttribute("data-id");
|
|
4358
|
+
preview.removeAttribute("data-task-path");
|
|
4359
|
+
preview.setAttribute("aria-hidden", "true");
|
|
4360
|
+
preview.inert = true;
|
|
4361
|
+
preview.style.width = `${bounds.width}px`;
|
|
4362
|
+
this.root.append(preview);
|
|
4363
|
+
this.listDragPreview = { element: preview, offsetX: s.x0 - bounds.left, offsetY: s.y0 - bounds.top, width: bounds.width, height: bounds.height };
|
|
4364
|
+
}
|
|
4365
|
+
el.classList.add("is-dragging");
|
|
4366
|
+
}
|
|
4171
4367
|
}
|
|
4172
4368
|
this.closeMore();
|
|
4173
4369
|
if (s.mode !== "select") this.unselectSilently();
|
|
4174
4370
|
this.root.classList.add("tnc-dragging", `tnc-dragging-${s.mode}`);
|
|
4175
4371
|
if (s.lastEvent) this.track(s, s.lastEvent);
|
|
4372
|
+
this.startDragScroll();
|
|
4373
|
+
}
|
|
4374
|
+
startDragScroll() {
|
|
4375
|
+
if (this.dragScrollFrame !== null) return;
|
|
4376
|
+
const tick = () => {
|
|
4377
|
+
this.dragScrollFrame = null;
|
|
4378
|
+
const session = this.session;
|
|
4379
|
+
const pointer = session?.lastEvent;
|
|
4380
|
+
if (!session?.active || !pointer) return;
|
|
4381
|
+
const origin = session.origin ? this.elementsFor(session.origin.id)[0] : this.viewHost;
|
|
4382
|
+
const underPointer = this.doc.elementFromPoint(pointer.clientX, pointer.clientY);
|
|
4383
|
+
let moved = false;
|
|
4384
|
+
if (origin && underPointer && this.root.contains(underPointer)) {
|
|
4385
|
+
for (const axis of ["x", "y"]) {
|
|
4386
|
+
const scroller = scrollerOf(origin, axis);
|
|
4387
|
+
if (!scroller) continue;
|
|
4388
|
+
const viewport = scroller === this.doc.scrollingElement ? { left: 0, top: 0, right: this.win.innerWidth, bottom: this.win.innerHeight } : scroller.getBoundingClientRect();
|
|
4389
|
+
const coordinate = axis === "x" ? pointer.clientX : pointer.clientY;
|
|
4390
|
+
const lower = Math.max(0, axis === "x" ? viewport.left : viewport.top);
|
|
4391
|
+
const upper = Math.min(axis === "x" ? this.win.innerWidth : this.win.innerHeight, axis === "x" ? viewport.right : viewport.bottom);
|
|
4392
|
+
if (coordinate < lower || coordinate > upper) {
|
|
4393
|
+
this.dragScrollEdges.delete(axis);
|
|
4394
|
+
continue;
|
|
4395
|
+
}
|
|
4396
|
+
const delta = coordinate < lower + 32 ? -Math.min(12, (lower + 32 - coordinate) / 2) : coordinate > upper - 32 ? Math.min(12, (coordinate - upper + 32) / 2) : 0;
|
|
4397
|
+
if (!delta) {
|
|
4398
|
+
this.dragScrollEdges.delete(axis);
|
|
4399
|
+
continue;
|
|
4400
|
+
}
|
|
4401
|
+
const edge = this.dragScrollEdges.get(axis);
|
|
4402
|
+
const direction = Math.sign(delta);
|
|
4403
|
+
const now = this.win.performance.now();
|
|
4404
|
+
if (!edge || edge.scroller !== scroller || edge.direction !== direction) {
|
|
4405
|
+
this.dragScrollEdges.set(axis, { scroller, direction, since: now });
|
|
4406
|
+
continue;
|
|
4407
|
+
}
|
|
4408
|
+
if (now - edge.since < 180) continue;
|
|
4409
|
+
if (!this.dragScrollOrigins.has(scroller)) this.dragScrollOrigins.set(scroller, { top: scroller.scrollTop, left: scroller.scrollLeft });
|
|
4410
|
+
const before = axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
|
|
4411
|
+
if (axis === "x") scroller.scrollLeft += delta;
|
|
4412
|
+
else scroller.scrollTop += delta;
|
|
4413
|
+
moved ||= before !== (axis === "x" ? scroller.scrollLeft : scroller.scrollTop);
|
|
4414
|
+
}
|
|
4415
|
+
} else this.dragScrollEdges.clear();
|
|
4416
|
+
if (moved) this.track(session, pointer);
|
|
4417
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4418
|
+
};
|
|
4419
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4176
4420
|
}
|
|
4421
|
+
onTouchStart = (ev) => {
|
|
4422
|
+
const first = ev.touches[0];
|
|
4423
|
+
const second = ev.touches[1];
|
|
4424
|
+
if (!first || !second) {
|
|
4425
|
+
this.endPinch();
|
|
4426
|
+
return;
|
|
4427
|
+
}
|
|
4428
|
+
const view = this.current;
|
|
4429
|
+
if (this.session?.active || !this.opts.pinchZoom || !(view instanceof TimeGridView)) return;
|
|
4430
|
+
const body = first.target.closest(".tnc-tg-body");
|
|
4431
|
+
if (!body?.contains(second.target)) return;
|
|
4432
|
+
this.endSession(null, true);
|
|
4433
|
+
if (this.pinch) return;
|
|
4434
|
+
const { top, height } = body.getBoundingClientRect();
|
|
4435
|
+
const y = (first.clientY + second.clientY) / 2;
|
|
4436
|
+
this.pinch = {
|
|
4437
|
+
body,
|
|
4438
|
+
distance: Math.max(1, Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY)),
|
|
4439
|
+
y,
|
|
4440
|
+
anchor: (y - top) / height,
|
|
4441
|
+
drawn: view.drawnSlotHeight,
|
|
4442
|
+
option: this.opts.slotHeight
|
|
4443
|
+
};
|
|
4444
|
+
};
|
|
4177
4445
|
onTouchMove = (ev) => {
|
|
4178
4446
|
if (this.session?.active) ev.preventDefault();
|
|
4447
|
+
const p = this.pinch;
|
|
4448
|
+
const first = ev.touches[0];
|
|
4449
|
+
const second = ev.touches[1];
|
|
4450
|
+
if (!p || !first || !second) return;
|
|
4451
|
+
if (!ev.cancelable) {
|
|
4452
|
+
this.endPinch();
|
|
4453
|
+
return;
|
|
4454
|
+
}
|
|
4455
|
+
ev.preventDefault();
|
|
4456
|
+
const height = p.drawn * Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY) / p.distance;
|
|
4457
|
+
p.y = (first.clientY + second.clientY) / 2;
|
|
4458
|
+
this.pinchTo(p, Math.min(Math.max(p.drawn, PINCH_MAX_SLOT), Math.max(Math.min(p.drawn, PINCH_MIN_SLOT), height)));
|
|
4459
|
+
};
|
|
4460
|
+
onTouchEnd = (ev) => {
|
|
4461
|
+
if (ev.touches.length < 2) this.endPinch();
|
|
4179
4462
|
};
|
|
4463
|
+
/** Rescales the grid, keeping the anchored time under the fingers. */
|
|
4464
|
+
pinchTo(p, slotHeight) {
|
|
4465
|
+
this.opts = { ...this.opts, slotHeight };
|
|
4466
|
+
if (!(this.current instanceof TimeGridView)) return;
|
|
4467
|
+
this.current.applyScale();
|
|
4468
|
+
const { top, height } = p.body.getBoundingClientRect();
|
|
4469
|
+
const scroller = scrollerOf(p.body, "y");
|
|
4470
|
+
if (scroller) scroller.scrollTop += top + height * p.anchor - p.y;
|
|
4471
|
+
}
|
|
4472
|
+
endPinch() {
|
|
4473
|
+
const p = this.pinch;
|
|
4474
|
+
this.pinch = null;
|
|
4475
|
+
if (!p?.body.isConnected) return;
|
|
4476
|
+
const settled = Math.round(this.opts.slotHeight);
|
|
4477
|
+
const slotHeight = settled === Math.round(p.drawn) ? p.option : settled;
|
|
4478
|
+
this.pinchTo(p, slotHeight);
|
|
4479
|
+
this.current?.render(null);
|
|
4480
|
+
if (slotHeight !== p.option) this.opts.onSlotHeightChange?.(slotHeight);
|
|
4481
|
+
}
|
|
4180
4482
|
onPointerMove = (ev) => {
|
|
4181
4483
|
const s = this.session;
|
|
4182
4484
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
@@ -4202,6 +4504,12 @@ var Calendar = class {
|
|
|
4202
4504
|
this.track(s, ev);
|
|
4203
4505
|
};
|
|
4204
4506
|
onPointerUp = (ev) => {
|
|
4507
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
4508
|
+
this.cancelledPointerId = null;
|
|
4509
|
+
this.suppressNextClick();
|
|
4510
|
+
this.listenDoc(false);
|
|
4511
|
+
return;
|
|
4512
|
+
}
|
|
4205
4513
|
const s = this.session;
|
|
4206
4514
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
4207
4515
|
if (!s.active) {
|
|
@@ -4214,28 +4522,49 @@ var Calendar = class {
|
|
|
4214
4522
|
return;
|
|
4215
4523
|
}
|
|
4216
4524
|
this.suppressNextClick();
|
|
4525
|
+
this.track(s, ev);
|
|
4217
4526
|
this.endSession(ev, false);
|
|
4218
4527
|
};
|
|
4219
4528
|
onPointerCancel = (ev) => {
|
|
4529
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
4530
|
+
this.cancelledPointerId = null;
|
|
4531
|
+
this.listenDoc(false);
|
|
4532
|
+
}
|
|
4220
4533
|
if (this.session && ev.pointerId === this.session.pointerId) this.endSession(null, true);
|
|
4221
4534
|
};
|
|
4222
4535
|
onDocKeyDown = (ev) => {
|
|
4223
4536
|
if (ev.key === "Escape" && this.session) {
|
|
4224
4537
|
ev.preventDefault();
|
|
4225
4538
|
ev.stopPropagation();
|
|
4539
|
+
const pointerId = this.session.active ? this.session.pointerId : null;
|
|
4226
4540
|
this.endSession(null, true);
|
|
4541
|
+
if (pointerId !== null) {
|
|
4542
|
+
this.cancelledPointerId = pointerId;
|
|
4543
|
+
this.listenDoc(true);
|
|
4544
|
+
}
|
|
4227
4545
|
}
|
|
4228
4546
|
};
|
|
4229
4547
|
/** Proposal for the current pointer position. */
|
|
4230
4548
|
track(s, ev) {
|
|
4549
|
+
if (this.listDragPreview) {
|
|
4550
|
+
const preview = this.listDragPreview;
|
|
4551
|
+
const bounds = this.root.getBoundingClientRect();
|
|
4552
|
+
preview.element.style.left = `${Math.max(8, Math.min(this.win.innerWidth - preview.width - 8, ev.clientX - preview.offsetX)) - bounds.left}px`;
|
|
4553
|
+
preview.element.style.top = `${Math.max(8, Math.min(this.win.innerHeight - preview.height - 8, ev.clientY - preview.offsetY)) - bounds.top}px`;
|
|
4554
|
+
}
|
|
4231
4555
|
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4232
4556
|
if (s.mode === "external") {
|
|
4233
4557
|
this.trackExternal(s, hit);
|
|
4234
4558
|
return;
|
|
4235
4559
|
}
|
|
4236
4560
|
if (!s.grab) s.grab = hit;
|
|
4237
|
-
if (!hit || !s.grab)
|
|
4561
|
+
if (!hit || !s.grab) {
|
|
4562
|
+
this.invalidateProposal(s);
|
|
4563
|
+
return;
|
|
4564
|
+
}
|
|
4238
4565
|
if (s.mode === "select") {
|
|
4566
|
+
s.denied = false;
|
|
4567
|
+
this.root.classList.remove("tnc-drop-denied");
|
|
4239
4568
|
const sel = this.selectionFor(s.grab, hit);
|
|
4240
4569
|
if (!sel) return;
|
|
4241
4570
|
this.selection = sel;
|
|
@@ -4244,25 +4573,40 @@ var Calendar = class {
|
|
|
4244
4573
|
}
|
|
4245
4574
|
const origin = s.origin;
|
|
4246
4575
|
const p = s.mode === "move" ? this.moveProposal(origin, s.grab, hit) : this.resizeProposal(origin, hit, s.mode === "resize-start");
|
|
4247
|
-
if (!p)
|
|
4576
|
+
if (!p) {
|
|
4577
|
+
this.invalidateProposal(s);
|
|
4578
|
+
return;
|
|
4579
|
+
}
|
|
4248
4580
|
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;
|
|
4249
4581
|
s.denied = !allowed;
|
|
4250
4582
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
4251
|
-
if (!allowed
|
|
4583
|
+
if (!allowed) {
|
|
4584
|
+
this.invalidateProposal(s);
|
|
4585
|
+
return;
|
|
4586
|
+
}
|
|
4587
|
+
if (sameProposal(p, s.proposal)) return;
|
|
4252
4588
|
s.proposal = p;
|
|
4253
4589
|
this.dragRow = p.rowId !== void 0 ? { id: origin.id, rowId: p.rowId } : null;
|
|
4254
|
-
this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4590
|
+
if (this.opts.view !== "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4591
|
+
}
|
|
4592
|
+
invalidateProposal(s) {
|
|
4593
|
+
const hadProposal = s.proposal !== null;
|
|
4594
|
+
s.proposal = null;
|
|
4595
|
+
s.hit = null;
|
|
4596
|
+
s.denied = true;
|
|
4597
|
+
this.dragRow = null;
|
|
4598
|
+
this.root.classList.add("tnc-drop-denied");
|
|
4599
|
+
this.strip?.markTarget(null);
|
|
4600
|
+
this.unselectSilently();
|
|
4601
|
+
if (s.mode === "external") this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
4602
|
+
else if (hadProposal && s.origin) this.applyChange(this.store.upsert([s.origin.item]));
|
|
4255
4603
|
}
|
|
4256
4604
|
trackExternal(s, raw) {
|
|
4257
4605
|
const src = s.source;
|
|
4258
4606
|
const hit = raw?.kind === "timeline" && raw.area !== "track" && raw.area !== "header" ? null : raw;
|
|
4259
4607
|
s.hit = hit;
|
|
4260
4608
|
if (!hit) {
|
|
4261
|
-
|
|
4262
|
-
s.proposal = null;
|
|
4263
|
-
this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
4264
|
-
}
|
|
4265
|
-
this.unselectSilently();
|
|
4609
|
+
this.invalidateProposal(s);
|
|
4266
4610
|
return;
|
|
4267
4611
|
}
|
|
4268
4612
|
const dur = src.durationMinutes ?? this.opts.defaultTimedMinutes;
|
|
@@ -4281,6 +4625,10 @@ var Calendar = class {
|
|
|
4281
4625
|
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;
|
|
4282
4626
|
s.denied = !allowed;
|
|
4283
4627
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
4628
|
+
if (!allowed) {
|
|
4629
|
+
this.invalidateProposal(s);
|
|
4630
|
+
return;
|
|
4631
|
+
}
|
|
4284
4632
|
if (sameProposal(p, s.proposal)) return;
|
|
4285
4633
|
s.proposal = p;
|
|
4286
4634
|
if (hit.kind === "timeline" && p.rowId === void 0) {
|
|
@@ -4473,7 +4821,19 @@ var Calendar = class {
|
|
|
4473
4821
|
endSession(ev, cancel) {
|
|
4474
4822
|
const s = this.session;
|
|
4475
4823
|
if (!s) return;
|
|
4824
|
+
if (this.dragScrollFrame !== null) this.win.cancelAnimationFrame(this.dragScrollFrame);
|
|
4825
|
+
this.dragScrollFrame = null;
|
|
4826
|
+
if (cancel || s.denied) for (const [scroller, position] of this.dragScrollOrigins) {
|
|
4827
|
+
scroller.scrollTop = position.top;
|
|
4828
|
+
scroller.scrollLeft = position.left;
|
|
4829
|
+
}
|
|
4830
|
+
this.dragScrollOrigins.clear();
|
|
4831
|
+
this.dragScrollEdges.clear();
|
|
4476
4832
|
this.session = null;
|
|
4833
|
+
this.listDragPreview?.element.remove();
|
|
4834
|
+
this.listDragPreview = null;
|
|
4835
|
+
for (const source of this.dragSources) source.remove();
|
|
4836
|
+
this.dragSources = [];
|
|
4477
4837
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
4478
4838
|
this.listenDoc(false);
|
|
4479
4839
|
this.root.classList.remove(
|
|
@@ -4487,10 +4847,15 @@ var Calendar = class {
|
|
|
4487
4847
|
);
|
|
4488
4848
|
const draggedId = this.draggingId;
|
|
4489
4849
|
this.draggingId = null;
|
|
4850
|
+
this.dragWidth = null;
|
|
4490
4851
|
this.dragRow = null;
|
|
4491
|
-
if (draggedId) for (const el of this.elementsFor(draggedId))
|
|
4852
|
+
if (draggedId) for (const el of this.elementsFor(draggedId)) {
|
|
4853
|
+
el.classList.remove("is-dragging");
|
|
4854
|
+
el.style.removeProperty("--tnc-drag-width");
|
|
4855
|
+
}
|
|
4492
4856
|
this.strip?.markTarget(null);
|
|
4493
4857
|
if (!s.active) return;
|
|
4858
|
+
if (this.current instanceof DayGridView) this.current.render(null);
|
|
4494
4859
|
if (s.mode === "select") {
|
|
4495
4860
|
if (cancel) this.unselectSilently();
|
|
4496
4861
|
else if (this.selection) this.fireSelect(this.selection, ev);
|
|
@@ -4542,10 +4907,16 @@ var Calendar = class {
|
|
|
4542
4907
|
revert();
|
|
4543
4908
|
return;
|
|
4544
4909
|
}
|
|
4910
|
+
if (this.opts.view === "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
4545
4911
|
this.flush();
|
|
4546
4912
|
this.commit(s.mode === "move" ? "drop" : "resize", origin, p, ev, revert);
|
|
4547
4913
|
}
|
|
4548
4914
|
commit(kind, origin, p, ev, revert) {
|
|
4915
|
+
let reverted = false;
|
|
4916
|
+
const mark = (state) => {
|
|
4917
|
+
this.itemWriteStates.set(origin.id, state);
|
|
4918
|
+
for (const element of this.elementsFor(origin.id)) this.applyItemWriteState(element, origin.id);
|
|
4919
|
+
};
|
|
4549
4920
|
const arg = {
|
|
4550
4921
|
item: origin.item,
|
|
4551
4922
|
start: new Date(p.startMs),
|
|
@@ -4561,19 +4932,32 @@ var Calendar = class {
|
|
|
4561
4932
|
jsEvent: ev,
|
|
4562
4933
|
source: ev && "key" in ev ? "keyboard" : "pointer",
|
|
4563
4934
|
view: this.view,
|
|
4564
|
-
revert
|
|
4935
|
+
revert: () => {
|
|
4936
|
+
reverted = true;
|
|
4937
|
+
revert();
|
|
4938
|
+
mark("failed");
|
|
4939
|
+
}
|
|
4565
4940
|
};
|
|
4566
4941
|
const handler = kind === "drop" ? this.opts.onItemDrop : this.opts.onItemResize;
|
|
4567
4942
|
try {
|
|
4943
|
+
mark("pending");
|
|
4568
4944
|
const res = handler?.(arg);
|
|
4569
4945
|
if (res && typeof res.then === "function") {
|
|
4570
|
-
Promise.resolve(res).
|
|
4571
|
-
|
|
4946
|
+
void Promise.resolve(res).then(() => {
|
|
4947
|
+
if (!reverted) mark("changed");
|
|
4948
|
+
}, () => arg.revert());
|
|
4949
|
+
} else if (!reverted) mark("changed");
|
|
4572
4950
|
} catch (err) {
|
|
4573
|
-
revert();
|
|
4951
|
+
arg.revert();
|
|
4574
4952
|
throw err;
|
|
4575
4953
|
}
|
|
4576
4954
|
}
|
|
4955
|
+
applyItemWriteState(element, id) {
|
|
4956
|
+
const state = this.itemWriteStates.get(id);
|
|
4957
|
+
for (const name of ["pending", "failed", "changed"]) element.classList.toggle(`is-write-${name}`, state === name);
|
|
4958
|
+
if (state === "pending") element.setAttribute("aria-busy", "true");
|
|
4959
|
+
else element.removeAttribute("aria-busy");
|
|
4960
|
+
}
|
|
4577
4961
|
/* --------------------------------------------------------- click / keys */
|
|
4578
4962
|
/** Runs in the capture phase so the click ending a drag never reaches content inside the calendar, React portals included. */
|
|
4579
4963
|
onClickCapture = (ev) => {
|
|
@@ -4583,6 +4967,12 @@ var Calendar = class {
|
|
|
4583
4967
|
ev.preventDefault();
|
|
4584
4968
|
};
|
|
4585
4969
|
onClick = (ev) => {
|
|
4970
|
+
const compactDay = ev.target.closest?.(".tnc-dg--compact .tnc-dg-day");
|
|
4971
|
+
if (compactDay && this.root.contains(compactDay) && !ev.target.closest(".tnc-navlink")) {
|
|
4972
|
+
const day = parseDay(compactDay.dataset.date ?? "");
|
|
4973
|
+
if (day !== null) this.openMore(day, compactDay, ev);
|
|
4974
|
+
return;
|
|
4975
|
+
}
|
|
4586
4976
|
const yearDay = ev.target.closest?.(".tnc-ym-day");
|
|
4587
4977
|
if (yearDay && this.root.contains(yearDay)) {
|
|
4588
4978
|
const day = parseDay(yearDay.dataset.date ?? "");
|
|
@@ -4671,7 +5061,8 @@ var Calendar = class {
|
|
|
4671
5061
|
const day = parseDay(target.dataset.date ?? "");
|
|
4672
5062
|
if (day !== null) {
|
|
4673
5063
|
ev.preventDefault();
|
|
4674
|
-
|
|
5064
|
+
if (target.closest(".tnc-dg--compact")) this.openMore(day, target, ev);
|
|
5065
|
+
else this.clickSlot({ kind: "day", day }, ev, true);
|
|
4675
5066
|
}
|
|
4676
5067
|
return;
|
|
4677
5068
|
}
|
|
@@ -4759,9 +5150,13 @@ var Calendar = class {
|
|
|
4759
5150
|
const dt = ev.dataTransfer;
|
|
4760
5151
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
4761
5152
|
ev.preventDefault();
|
|
5153
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
5154
|
+
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") {
|
|
5155
|
+
dt.dropEffect = "none";
|
|
5156
|
+
this.unselectSilently();
|
|
5157
|
+
return;
|
|
5158
|
+
}
|
|
4762
5159
|
dt.dropEffect = "copy";
|
|
4763
|
-
const hit = this.current.hit(ev.clientX, ev.clientY);
|
|
4764
|
-
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
4765
5160
|
const sel = this.selectionFor(hit, hit);
|
|
4766
5161
|
if (!sel) return;
|
|
4767
5162
|
this.selection = sel;
|
|
@@ -4775,7 +5170,7 @@ var Calendar = class {
|
|
|
4775
5170
|
const dt = ev.dataTransfer;
|
|
4776
5171
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
4777
5172
|
ev.preventDefault();
|
|
4778
|
-
const hit = this.
|
|
5173
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4779
5174
|
this.unselectSilently();
|
|
4780
5175
|
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
4781
5176
|
const sel = this.selectionFor(hit, hit);
|
|
@@ -4806,7 +5201,7 @@ function scrollerOf(el, axis) {
|
|
|
4806
5201
|
return doc.scrollingElement;
|
|
4807
5202
|
}
|
|
4808
5203
|
function rangeMoved(prev, next) {
|
|
4809
|
-
return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.endDay !== next.dataRange.endDay;
|
|
5204
|
+
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;
|
|
4810
5205
|
}
|
|
4811
5206
|
|
|
4812
5207
|
// src/tasknotes/bases.ts
|
|
@@ -4849,6 +5244,8 @@ var BASES_CALENDAR_DEFAULTS = {
|
|
|
4849
5244
|
slotDuration: "00:30:00",
|
|
4850
5245
|
snapDuration: "00:30:00",
|
|
4851
5246
|
scrollTime: "08:00:00",
|
|
5247
|
+
slotHeight: 24,
|
|
5248
|
+
pinchZoom: true,
|
|
4852
5249
|
nowIndicator: true,
|
|
4853
5250
|
showTimeGrid: true,
|
|
4854
5251
|
selectMirror: true,
|
|
@@ -4913,6 +5310,8 @@ function basesLayoutOptions(s, context = {}) {
|
|
|
4913
5310
|
slotDuration,
|
|
4914
5311
|
snapDuration: time("snapDuration") ?? slotDuration,
|
|
4915
5312
|
scrollTime: time("scrollTime", 48) ?? d.scrollTime,
|
|
5313
|
+
slotHeight: clamp(Math.round(num(s.slotHeight, d.slotHeight)), 5, 96),
|
|
5314
|
+
pinchZoom: pick("pinchZoom") !== false,
|
|
4916
5315
|
nowIndicator: pick("nowIndicator") !== false,
|
|
4917
5316
|
timeGrid: showTimeGrid,
|
|
4918
5317
|
selectMirror: pick("selectMirror") !== false,
|