@tasknotes/calendar 0.1.5 → 0.1.7
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 +24 -0
- package/README.md +31 -0
- package/dist/calendar.css +74 -19
- package/dist/cjs/core.cjs +19 -2
- package/dist/cjs/index.cjs +235 -41
- package/dist/cjs/react.cjs +235 -41
- package/dist/esm/{chunk-DVMZ5OXN.js → chunk-66A6EMCU.js} +19 -2
- package/dist/esm/{chunk-25TY2UVR.js → chunk-EDRCVOY7.js} +217 -40
- package/dist/esm/core.js +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/react.js +2 -2
- package/dist/types/core/layout.d.cts +5 -0
- package/dist/types/core/layout.d.ts +5 -0
- package/dist/types/dom/calendar.d.cts +5 -1
- package/dist/types/dom/calendar.d.ts +5 -1
- package/dist/types/dom/context.d.cts +1 -1
- package/dist/types/dom/context.d.ts +1 -1
- package/dist/types/dom/options.d.cts +3 -0
- package/dist/types/dom/options.d.ts +3 -0
- package/dist/types/dom/views/timegrid.d.cts +4 -0
- package/dist/types/dom/views/timegrid.d.ts +4 -0
- package/package.json +1 -1
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
wallTime,
|
|
28
28
|
weekdayOf,
|
|
29
29
|
ymdOfDay
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-66A6EMCU.js";
|
|
31
31
|
|
|
32
32
|
// src/dom/format.ts
|
|
33
33
|
var Formatter = class {
|
|
@@ -152,6 +152,7 @@ var DEFAULT_TEXT = {
|
|
|
152
152
|
allDay: "All day",
|
|
153
153
|
noItems: "Nothing scheduled",
|
|
154
154
|
more: (n) => `+${n} more`,
|
|
155
|
+
appointments: (n) => `${n} appointments`,
|
|
155
156
|
itemCount: (n) => n === 1 ? "1 item" : `${n} items`,
|
|
156
157
|
weekNumber: (n) => `W${n}`,
|
|
157
158
|
views: { week: "Week", day: "Day", days: "Days", month: "Month", list: "List", year: "Year", timeline: "Timeline" },
|
|
@@ -221,6 +222,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
221
222
|
"dateStrip",
|
|
222
223
|
"listLeadTitle",
|
|
223
224
|
"listAddText",
|
|
225
|
+
"listEmptyContent",
|
|
224
226
|
"monthDisplay",
|
|
225
227
|
"timeZone",
|
|
226
228
|
"locale",
|
|
@@ -546,19 +548,27 @@ var DayGridView = class {
|
|
|
546
548
|
});
|
|
547
549
|
}
|
|
548
550
|
w.pool.end();
|
|
549
|
-
w.more.
|
|
551
|
+
const existing = new Map(Array.from(w.more.children, (element) => [element.getAttribute("data-day"), element]));
|
|
550
552
|
hidden.forEach((items, col) => {
|
|
551
553
|
if (!items.length) return;
|
|
552
554
|
const day = w.days[col];
|
|
553
|
-
const
|
|
555
|
+
const key = String(day);
|
|
556
|
+
let link = existing.get(key);
|
|
557
|
+
if (!link) {
|
|
558
|
+
link = h(ctx.doc, "button", "tnc-more", { type: "button", "aria-haspopup": "dialog", "data-day": key });
|
|
559
|
+
const anchor = link;
|
|
560
|
+
link.addEventListener("click", (ev) => ctx.openMore(day, anchor, ev));
|
|
561
|
+
w.more.append(link);
|
|
562
|
+
}
|
|
563
|
+
existing.delete(key);
|
|
564
|
+
link.textContent = ctx.text.more(items.length);
|
|
554
565
|
setStyle(link, {
|
|
555
566
|
left: `calc(${pct(col / n)} + 2px)`,
|
|
556
567
|
width: `calc(${pct(1 / n)} - 4px)`,
|
|
557
568
|
top: `calc(var(--tnc-dg-top-h) + ${visible(col)} * (var(--tnc-bar-h) + ${BAR_GAP}px))`
|
|
558
569
|
});
|
|
559
|
-
link.addEventListener("click", (ev) => ctx.openMore(day, link, ev));
|
|
560
|
-
w.more.append(link);
|
|
561
570
|
});
|
|
571
|
+
for (const link of existing.values()) link.remove();
|
|
562
572
|
const step = this.barPx + BAR_GAP;
|
|
563
573
|
let need = maxLevel >= 0 ? (maxLevel + 1) * step - BAR_GAP : 0;
|
|
564
574
|
hidden.forEach((items, col) => {
|
|
@@ -761,6 +771,8 @@ var ListView = class {
|
|
|
761
771
|
if (!keep.size && !leadItems.length) {
|
|
762
772
|
if (!empty) {
|
|
763
773
|
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
774
|
+
const content = ctx.opts.listEmptyContent?.();
|
|
775
|
+
if (content) empty.replaceChildren(content);
|
|
764
776
|
this.body.append(empty);
|
|
765
777
|
}
|
|
766
778
|
} else empty?.remove();
|
|
@@ -1096,6 +1108,11 @@ var DateStrip = class {
|
|
|
1096
1108
|
|
|
1097
1109
|
// src/dom/views/timegrid.ts
|
|
1098
1110
|
var BAR_GAP2 = 2;
|
|
1111
|
+
function inLane(lane, split, left, width) {
|
|
1112
|
+
const [x, w] = !split ? ["1px", "100% - 9px"] : lane === "plan" ? ["1px", "100% - var(--tnc-actual-w) - 5px"] : ["100% - var(--tnc-actual-w) - 2px", "var(--tnc-actual-w)"];
|
|
1113
|
+
const f = (n) => Math.round(n * 1e4) / 1e4;
|
|
1114
|
+
return { left: `calc(${x} + (${w}) * ${f(left)})`, width: `calc((${w}) * ${f(width)} - 1px)` };
|
|
1115
|
+
}
|
|
1099
1116
|
var TimeGridView = class {
|
|
1100
1117
|
constructor(ctx) {
|
|
1101
1118
|
this.ctx = ctx;
|
|
@@ -1104,6 +1121,8 @@ var TimeGridView = class {
|
|
|
1104
1121
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1105
1122
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1106
1123
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1124
|
+
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1125
|
+
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
1107
1126
|
this.top = h(doc, "div", "tnc-tg-top");
|
|
1108
1127
|
this.headRow = h(doc, "div", "tnc-tg-head tnc-tg-cols", { role: "row" });
|
|
1109
1128
|
this.top.append(this.headRow);
|
|
@@ -1131,7 +1150,7 @@ var TimeGridView = class {
|
|
|
1131
1150
|
} else {
|
|
1132
1151
|
this.body = this.gutter = null;
|
|
1133
1152
|
}
|
|
1134
|
-
this.el.append(this.scroller);
|
|
1153
|
+
this.el.append(this.dayNavigation, this.scroller);
|
|
1135
1154
|
this.build();
|
|
1136
1155
|
if (showGrid && opts.height === "fill") {
|
|
1137
1156
|
const RO = ctx.win.ResizeObserver;
|
|
@@ -1144,6 +1163,7 @@ var TimeGridView = class {
|
|
|
1144
1163
|
ctx;
|
|
1145
1164
|
el;
|
|
1146
1165
|
scroller;
|
|
1166
|
+
dayNavigation;
|
|
1147
1167
|
top;
|
|
1148
1168
|
headRow;
|
|
1149
1169
|
allDayRow;
|
|
@@ -1199,6 +1219,9 @@ var TimeGridView = class {
|
|
|
1199
1219
|
head.append(cap);
|
|
1200
1220
|
}
|
|
1201
1221
|
this.headRow.append(head);
|
|
1222
|
+
const dayButton = h(doc, "button", "tnc-btn", { type: "button", "aria-label": ctx.fmt.longDate(day) }, `${ctx.fmt.weekdayShort(day)} ${ctx.fmt.dayNumber(day)}`);
|
|
1223
|
+
dayButton.addEventListener("click", () => this.revealDay(day));
|
|
1224
|
+
this.dayNavigation.append(dayButton);
|
|
1202
1225
|
opts.dayHeaderDidMount?.({ ...arg, el: head });
|
|
1203
1226
|
const col = h(doc, "div", ["tnc-col", ...ctx.dayClasses(day)].join(" "), { role: "cell", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr });
|
|
1204
1227
|
const bgLayer = h(doc, "div", "tnc-col-bg");
|
|
@@ -1215,6 +1238,7 @@ var TimeGridView = class {
|
|
|
1215
1238
|
this.body?.append(col);
|
|
1216
1239
|
return {
|
|
1217
1240
|
day,
|
|
1241
|
+
collisionColumns: 1,
|
|
1218
1242
|
head,
|
|
1219
1243
|
col,
|
|
1220
1244
|
plan: new ItemPool(ctx, planLayer),
|
|
@@ -1271,12 +1295,17 @@ var TimeGridView = class {
|
|
|
1271
1295
|
/* --------------------------------------------------------------- render */
|
|
1272
1296
|
render(change) {
|
|
1273
1297
|
this.renderAllDay();
|
|
1274
|
-
if (!this.body)
|
|
1298
|
+
if (!this.body) {
|
|
1299
|
+
this.updateDayNavigation();
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1275
1302
|
const dirty = change ? change.days : null;
|
|
1276
1303
|
for (const c of this.cols) {
|
|
1277
1304
|
if (dirty && !dirty.has(c.day) && !(this.ctx.slot.max > MINUTES_PER_DAY && dirty.has(c.day + 1))) continue;
|
|
1278
1305
|
this.renderColumn(c);
|
|
1279
1306
|
}
|
|
1307
|
+
this.el.style.setProperty("--tnc-collision-columns", String(Math.max(1, ...this.cols.map((column) => column.collisionColumns))));
|
|
1308
|
+
this.updateDayNavigation();
|
|
1280
1309
|
this.renderCapacity();
|
|
1281
1310
|
this.tick();
|
|
1282
1311
|
}
|
|
@@ -1394,6 +1423,11 @@ var TimeGridView = class {
|
|
|
1394
1423
|
}
|
|
1395
1424
|
const minMinutes = opts.eventMinHeight / this.ppm;
|
|
1396
1425
|
const layout = layoutTimedDay(items, c.day, zone, minMinutes, ctx.order, [slot.min, slot.max]);
|
|
1426
|
+
c.collisionColumns = Math.max(
|
|
1427
|
+
1,
|
|
1428
|
+
...layout.plan.map((placement) => opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols),
|
|
1429
|
+
...layout.actual.filter((placement) => !placement.split).map((placement) => placement.cols)
|
|
1430
|
+
);
|
|
1397
1431
|
const total = this.minutes;
|
|
1398
1432
|
const top = (m) => pct((m - slot.min) / total);
|
|
1399
1433
|
const height = (s) => pct((s.endMin - s.startMin) / total);
|
|
@@ -1405,19 +1439,28 @@ var TimeGridView = class {
|
|
|
1405
1439
|
c.bg.end();
|
|
1406
1440
|
const hasActual = layout.actual.length > 0;
|
|
1407
1441
|
c.col.classList.toggle("has-actual", hasActual);
|
|
1408
|
-
|
|
1409
|
-
this.placeLane(c.
|
|
1442
|
+
for (const link of c.more.children) link.setAttribute("data-stale", "true");
|
|
1443
|
+
this.placeLane(c.plan, layout.plan, c, top, height, "plan");
|
|
1444
|
+
this.placeLane(c.actual, layout.actual, c, top, height, "actual");
|
|
1445
|
+
for (const link of [...c.more.children]) if (link.hasAttribute("data-stale")) link.remove();
|
|
1410
1446
|
}
|
|
1411
|
-
placeLane(pool, placements, c, top, height,
|
|
1447
|
+
placeLane(pool, placements, c, top, height, lane) {
|
|
1412
1448
|
const { ctx } = this;
|
|
1413
1449
|
const { opts } = ctx;
|
|
1414
|
-
const
|
|
1450
|
+
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
1451
|
+
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
1452
|
+
const collapse = placements.some((placement) => {
|
|
1453
|
+
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
1454
|
+
return columns > 1 && columns * readableWidth > laneWidth;
|
|
1455
|
+
});
|
|
1456
|
+
const withMore = lane === "plan" || collapse;
|
|
1457
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
1415
1458
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
1416
1459
|
pool.begin();
|
|
1417
1460
|
for (const p of placements) {
|
|
1418
1461
|
const preview = p.item.item.preview || p.item.id === ctx.draggingId;
|
|
1419
1462
|
if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
|
|
1420
|
-
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, items: [] };
|
|
1463
|
+
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
|
|
1421
1464
|
rec.count++;
|
|
1422
1465
|
rec.start = Math.min(rec.start, p.startMin);
|
|
1423
1466
|
rec.end = Math.max(rec.end, p.endMin);
|
|
@@ -1436,24 +1479,39 @@ var TimeGridView = class {
|
|
|
1436
1479
|
setStyle(el, {
|
|
1437
1480
|
top: top(p.startMin),
|
|
1438
1481
|
height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
1439
|
-
left
|
|
1440
|
-
width:
|
|
1482
|
+
...inLane(lane, p.split, left, width),
|
|
1483
|
+
"--tnc-event-readable-width": pct(span / cols / width),
|
|
1441
1484
|
"z-index": String(p.col + 1)
|
|
1442
1485
|
});
|
|
1443
1486
|
}
|
|
1444
1487
|
pool.end();
|
|
1445
1488
|
if (!withMore) return;
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1489
|
+
for (const [cluster, rec] of hiddenByCluster) {
|
|
1490
|
+
const key = `${lane}-${cluster}`;
|
|
1491
|
+
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
1492
|
+
if (!link) {
|
|
1493
|
+
link = h(ctx.doc, "button", "tnc-more tnc-more--stack", { type: "button", "aria-haspopup": "dialog", "data-cluster": key });
|
|
1494
|
+
const anchor = link;
|
|
1495
|
+
link.addEventListener("click", (ev) => ctx.openMore(c.day, anchor, ev, JSON.parse(anchor.dataset.itemIds ?? "[]")));
|
|
1496
|
+
c.more.append(link);
|
|
1497
|
+
}
|
|
1498
|
+
link.removeAttribute("data-stale");
|
|
1499
|
+
link.dataset.itemIds = JSON.stringify(placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.id));
|
|
1500
|
+
link.classList.toggle("tnc-more--collision", collapse);
|
|
1501
|
+
link.textContent = `+${rec.count}`;
|
|
1502
|
+
if (collapse) {
|
|
1503
|
+
link.textContent = ctx.text.appointments(rec.count);
|
|
1504
|
+
link.setAttribute("aria-label", link.textContent);
|
|
1505
|
+
link.title = link.getAttribute("aria-label");
|
|
1506
|
+
} else {
|
|
1507
|
+
link.removeAttribute("aria-label");
|
|
1508
|
+
link.removeAttribute("title");
|
|
1509
|
+
}
|
|
1449
1510
|
const cols = maxStack;
|
|
1450
1511
|
setStyle(link, {
|
|
1451
1512
|
top: top(rec.start),
|
|
1452
|
-
|
|
1453
|
-
width: `calc(${pct(1 / cols)} - 1px)`
|
|
1513
|
+
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
1454
1514
|
});
|
|
1455
|
-
link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
|
|
1456
|
-
c.more.append(link);
|
|
1457
1515
|
}
|
|
1458
1516
|
}
|
|
1459
1517
|
meta(s, bg) {
|
|
@@ -1564,6 +1622,26 @@ var TimeGridView = class {
|
|
|
1564
1622
|
}
|
|
1565
1623
|
resize() {
|
|
1566
1624
|
this.fitRows();
|
|
1625
|
+
this.render(null);
|
|
1626
|
+
}
|
|
1627
|
+
revealDay(day) {
|
|
1628
|
+
const column = this.cols.find((candidate) => candidate.day === day);
|
|
1629
|
+
if (!column) return;
|
|
1630
|
+
const bounds = column.head.getBoundingClientRect();
|
|
1631
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
1632
|
+
this.scroller.scrollLeft += bounds.left - viewport.left - (viewport.width - bounds.width) / 2;
|
|
1633
|
+
this.updateDayNavigation();
|
|
1634
|
+
}
|
|
1635
|
+
updateDayNavigation() {
|
|
1636
|
+
this.dayNavigation.hidden = this.scroller.scrollWidth <= this.scroller.clientWidth + 1;
|
|
1637
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
1638
|
+
this.cols.forEach((column, index) => {
|
|
1639
|
+
const bounds = column.head.getBoundingClientRect();
|
|
1640
|
+
const visible = bounds.left >= viewport.left && bounds.right <= viewport.right;
|
|
1641
|
+
const button = this.dayNavigation.children[index];
|
|
1642
|
+
button?.classList.toggle("is-offscreen", !visible);
|
|
1643
|
+
button?.setAttribute("aria-pressed", String(visible));
|
|
1644
|
+
});
|
|
1567
1645
|
}
|
|
1568
1646
|
scrollToTime(minute) {
|
|
1569
1647
|
if (!this.body) return;
|
|
@@ -2236,6 +2314,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2236
2314
|
|
|
2237
2315
|
// src/dom/calendar.ts
|
|
2238
2316
|
var EXTERNAL_ID = "__tnc_external__";
|
|
2317
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
2239
2318
|
var MAX_AGENDA_DAYS = 366;
|
|
2240
2319
|
function sameOption(a, b, depth = 0) {
|
|
2241
2320
|
if (a === b) return true;
|
|
@@ -2328,7 +2407,11 @@ var Calendar = class {
|
|
|
2328
2407
|
this.resizeObs?.disconnect();
|
|
2329
2408
|
const RO = this.win.ResizeObserver;
|
|
2330
2409
|
if (RO) {
|
|
2331
|
-
this.resizeObs = new RO(() =>
|
|
2410
|
+
this.resizeObs = new RO(() => {
|
|
2411
|
+
this.syncToolbarViewDisplay();
|
|
2412
|
+
this.current?.resize?.();
|
|
2413
|
+
this.positionMore();
|
|
2414
|
+
});
|
|
2332
2415
|
this.resizeObs.observe(this.root);
|
|
2333
2416
|
}
|
|
2334
2417
|
}
|
|
@@ -2348,9 +2431,11 @@ var Calendar = class {
|
|
|
2348
2431
|
if (this.frame !== null) old.cancelAnimationFrame(this.frame);
|
|
2349
2432
|
this.timer = this.frame = null;
|
|
2350
2433
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
2434
|
+
this.listenMoreGeometry(false);
|
|
2351
2435
|
this.doc = doc;
|
|
2352
2436
|
this.win = doc.defaultView ?? old;
|
|
2353
2437
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
2438
|
+
this.listenMoreGeometry(true);
|
|
2354
2439
|
this.observeResize();
|
|
2355
2440
|
this.rebuild(false);
|
|
2356
2441
|
this.scheduleTick();
|
|
@@ -2468,6 +2553,7 @@ var Calendar = class {
|
|
|
2468
2553
|
goToday() {
|
|
2469
2554
|
this.anchor = this.todayDay();
|
|
2470
2555
|
this.rebuild(true);
|
|
2556
|
+
if (this.current instanceof TimeGridView) this.current.revealDay(this.anchor);
|
|
2471
2557
|
}
|
|
2472
2558
|
/** The anchor date, as local midnight of that civil day in the calendar zone. */
|
|
2473
2559
|
getDate() {
|
|
@@ -2486,6 +2572,7 @@ var Calendar = class {
|
|
|
2486
2572
|
/** Re-measures (e.g. after the host was shown or resized by a parent). */
|
|
2487
2573
|
updateSize() {
|
|
2488
2574
|
this.current?.resize?.();
|
|
2575
|
+
this.positionMore();
|
|
2489
2576
|
}
|
|
2490
2577
|
getScroll() {
|
|
2491
2578
|
return this.current?.getScroll() ?? { top: 0, left: 0 };
|
|
@@ -2517,7 +2604,10 @@ var Calendar = class {
|
|
|
2517
2604
|
}
|
|
2518
2605
|
const view = this.current;
|
|
2519
2606
|
if (view instanceof TimelineView) view.scrollToInstant(ms, opts.align ?? "center", opts.smooth ?? false);
|
|
2520
|
-
else if (view instanceof TimeGridView
|
|
2607
|
+
else if (view instanceof TimeGridView) {
|
|
2608
|
+
view.revealDay(day);
|
|
2609
|
+
if (p.kind !== "day") view.scrollToTime(wallTime(ms, this.zone).minute);
|
|
2610
|
+
}
|
|
2521
2611
|
}
|
|
2522
2612
|
/** What is under a client point: a time slot, a day, or a timeline row and date. Null outside the view. */
|
|
2523
2613
|
hitTest(clientX, clientY) {
|
|
@@ -2692,9 +2782,10 @@ var Calendar = class {
|
|
|
2692
2782
|
if (meta) this.opts.itemWillUnmount?.(this.itemArg(el, meta));
|
|
2693
2783
|
el.remove();
|
|
2694
2784
|
}
|
|
2695
|
-
openMore(day, anchor, jsEvent) {
|
|
2785
|
+
openMore(day, anchor, jsEvent, itemIds) {
|
|
2696
2786
|
jsEvent.stopPropagation();
|
|
2697
|
-
const
|
|
2787
|
+
const group = itemIds ? new Set(itemIds) : null;
|
|
2788
|
+
const items = this.store.inRange(day, day + 1).filter((item) => !group || group.has(item.id)).filter((n) => n.item.display !== "background" && (n.allDay || anchor.classList.contains("tnc-more--stack") || this.opts.view === "month" || !this.opts.timeGrid || this.opts.eventMaxStack > 0)).sort((a, b) => Number(b.allDay) - Number(a.allDay) || this.order(a, b));
|
|
2698
2789
|
const shown = new Set(
|
|
2699
2790
|
[...this.viewHost.querySelectorAll(".tnc-item")].filter((el2) => this.meta.get(el2)?.day === day || (this.meta.get(el2)?.n.startDay ?? Infinity) <= day).map((el2) => el2.dataset.id ?? "")
|
|
2700
2791
|
);
|
|
@@ -2723,15 +2814,7 @@ var Calendar = class {
|
|
|
2723
2814
|
}
|
|
2724
2815
|
pool.end();
|
|
2725
2816
|
this.popover = { el, pool, anchor, day };
|
|
2726
|
-
|
|
2727
|
-
const ar = anchor.getBoundingClientRect();
|
|
2728
|
-
const w = Math.max(220, ar.width + 24);
|
|
2729
|
-
let left = ar.left - rr.left - 12;
|
|
2730
|
-
left = Math.max(4, Math.min(left, rr.width - w - 4));
|
|
2731
|
-
let top = ar.top - rr.top - 40;
|
|
2732
|
-
const height = el.offsetHeight;
|
|
2733
|
-
top = Math.max(4, Math.min(top, rr.height - height - 4));
|
|
2734
|
-
setStyle(el, { left, top, width: w });
|
|
2817
|
+
this.positionMore();
|
|
2735
2818
|
el.focus();
|
|
2736
2819
|
}
|
|
2737
2820
|
/* =============================================================== render */
|
|
@@ -2855,8 +2938,10 @@ var Calendar = class {
|
|
|
2855
2938
|
this.current.setScroll({ top: keep.top, left: 0 });
|
|
2856
2939
|
this.current.placeInstant(keep.ms, keep.vx);
|
|
2857
2940
|
} else if (scroll) this.current.setScroll(scroll);
|
|
2858
|
-
else if (this.current instanceof TimeGridView)
|
|
2859
|
-
|
|
2941
|
+
else if (this.current instanceof TimeGridView) {
|
|
2942
|
+
this.current.scrollToTime(this.slot.scroll);
|
|
2943
|
+
this.current.revealDay(this.anchor);
|
|
2944
|
+
} else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
2860
2945
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
2861
2946
|
}
|
|
2862
2947
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -3004,28 +3089,65 @@ var Calendar = class {
|
|
|
3004
3089
|
if (e.icon) b.replaceChildren(e.icon());
|
|
3005
3090
|
return b;
|
|
3006
3091
|
});
|
|
3092
|
+
const chevron = (d) => {
|
|
3093
|
+
const svg = this.doc.createElementNS(SVG_NS, "svg");
|
|
3094
|
+
setAttrs(svg, { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false" });
|
|
3095
|
+
const path = this.doc.createElementNS(SVG_NS, "path");
|
|
3096
|
+
path.setAttribute("d", d);
|
|
3097
|
+
svg.append(path);
|
|
3098
|
+
return svg;
|
|
3099
|
+
};
|
|
3100
|
+
const prev = btn("", this.text.prev, "tnc-btn-prev", () => this.prev());
|
|
3101
|
+
prev.append(chevron("m15 18-6-6 6-6"));
|
|
3102
|
+
const next = btn("", this.text.next, "tnc-btn-next", () => this.next());
|
|
3103
|
+
next.append(chevron("m9 18 6-6-6-6"));
|
|
3007
3104
|
const nav = h(
|
|
3008
3105
|
this.doc,
|
|
3009
3106
|
"div",
|
|
3010
3107
|
"tnc-toolbar-group tnc-toolbar-nav",
|
|
3011
3108
|
null,
|
|
3012
|
-
|
|
3109
|
+
prev,
|
|
3013
3110
|
btn(this.text.today, this.text.today, "tnc-btn-today", () => this.goToday()),
|
|
3014
|
-
|
|
3111
|
+
next
|
|
3015
3112
|
);
|
|
3016
3113
|
const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
|
|
3017
3114
|
const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
|
|
3115
|
+
const viewSelect = h(this.doc, "select", "tnc-btn tnc-toolbar-view-select", {
|
|
3116
|
+
"aria-label": this.text.viewHints[this.opts.view] ?? this.text.views[this.opts.view] ?? this.opts.view
|
|
3117
|
+
});
|
|
3018
3118
|
for (const v of views) {
|
|
3019
3119
|
const label = v === "days" ? this.text.views.days ?? `${this.opts.dayCount} days` : this.text.views[v] ?? v;
|
|
3020
3120
|
viewGroup.append(btn(label, this.text.viewHints[v] ?? label, `tnc-btn-view tnc-btn-view-${v}`, () => this.changeView(v), v === this.opts.view));
|
|
3121
|
+
viewSelect.append(h(this.doc, "option", "", { value: v }, label));
|
|
3021
3122
|
}
|
|
3123
|
+
viewSelect.value = this.opts.view;
|
|
3124
|
+
viewSelect.addEventListener("change", () => this.changeView(viewSelect.value));
|
|
3022
3125
|
const start = h(this.doc, "div", "tnc-toolbar-start");
|
|
3023
3126
|
const end = h(this.doc, "div", "tnc-toolbar-end");
|
|
3024
3127
|
if (cfg.extraPlacement === "start") start.append(...extras);
|
|
3025
3128
|
start.append(nav, title);
|
|
3026
|
-
end.append(viewGroup);
|
|
3129
|
+
end.append(viewGroup, viewSelect);
|
|
3027
3130
|
if (cfg.extraPlacement !== "start") end.append(...extras);
|
|
3028
3131
|
this.toolbarEl.append(start, end);
|
|
3132
|
+
this.syncToolbarViewDisplay();
|
|
3133
|
+
}
|
|
3134
|
+
syncToolbarViewDisplay() {
|
|
3135
|
+
const buttons = this.toolbarEl.querySelector(".tnc-toolbar-views");
|
|
3136
|
+
const select = this.toolbarEl.querySelector(".tnc-toolbar-view-select");
|
|
3137
|
+
const start = this.toolbarEl.querySelector(".tnc-toolbar-start");
|
|
3138
|
+
if (!buttons || !select || !start) return;
|
|
3139
|
+
const config = this.opts.toolbar;
|
|
3140
|
+
const mode = typeof config === "object" ? config.viewDisplay ?? "auto" : "auto";
|
|
3141
|
+
buttons.hidden = false;
|
|
3142
|
+
select.hidden = true;
|
|
3143
|
+
buttons.style.display = "flex";
|
|
3144
|
+
select.style.display = "none";
|
|
3145
|
+
const startWidth = Array.from(start.children).reduce((width, child) => width + child.scrollWidth, 0);
|
|
3146
|
+
const compact = mode === "select" || mode === "auto" && startWidth + buttons.scrollWidth + 32 > this.root.clientWidth;
|
|
3147
|
+
buttons.hidden = compact;
|
|
3148
|
+
select.hidden = !compact;
|
|
3149
|
+
buttons.style.display = compact ? "none" : "flex";
|
|
3150
|
+
select.style.display = compact ? "" : "none";
|
|
3029
3151
|
}
|
|
3030
3152
|
applyChange(change) {
|
|
3031
3153
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3158,6 +3280,38 @@ var Calendar = class {
|
|
|
3158
3280
|
}
|
|
3159
3281
|
}
|
|
3160
3282
|
/* ============================================================== popover */
|
|
3283
|
+
onMoreGeometryChange = () => this.positionMore();
|
|
3284
|
+
listenMoreGeometry(on) {
|
|
3285
|
+
const method = on ? "addEventListener" : "removeEventListener";
|
|
3286
|
+
this.doc[method]("scroll", this.onMoreGeometryChange, true);
|
|
3287
|
+
this.win[method]("resize", this.onMoreGeometryChange);
|
|
3288
|
+
this.win.visualViewport?.[method]("resize", this.onMoreGeometryChange);
|
|
3289
|
+
this.win.visualViewport?.[method]("scroll", this.onMoreGeometryChange);
|
|
3290
|
+
}
|
|
3291
|
+
positionMore() {
|
|
3292
|
+
const popover = this.popover;
|
|
3293
|
+
if (!popover) return;
|
|
3294
|
+
if (!popover.anchor.isConnected) {
|
|
3295
|
+
this.closeMore();
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
const viewport = this.win.visualViewport;
|
|
3299
|
+
const root = this.root.getBoundingClientRect();
|
|
3300
|
+
const anchor = popover.anchor.getBoundingClientRect();
|
|
3301
|
+
const leftEdge = Math.max(root.left, viewport?.offsetLeft ?? 0) + 4;
|
|
3302
|
+
const topEdge = Math.max(root.top, viewport?.offsetTop ?? 0) + 4;
|
|
3303
|
+
const rightEdge = Math.min(root.right, (viewport?.offsetLeft ?? 0) + (viewport?.width ?? this.win.innerWidth)) - 4;
|
|
3304
|
+
const bottomEdge = Math.min(root.bottom, (viewport?.offsetTop ?? 0) + (viewport?.height ?? this.win.innerHeight)) - 4;
|
|
3305
|
+
if (rightEdge <= leftEdge || bottomEdge <= topEdge) {
|
|
3306
|
+
this.closeMore();
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
const width = Math.min(360, rightEdge - leftEdge);
|
|
3310
|
+
setStyle(popover.el, { width, "max-height": Math.min(420, bottomEdge - topEdge) });
|
|
3311
|
+
const left = Math.max(leftEdge, Math.min(anchor.left - 12, rightEdge - width));
|
|
3312
|
+
const top = Math.max(topEdge, Math.min(anchor.top - 40, bottomEdge - popover.el.offsetHeight));
|
|
3313
|
+
setStyle(popover.el, { left: left - root.left, top: top - root.top });
|
|
3314
|
+
}
|
|
3161
3315
|
refreshMore() {
|
|
3162
3316
|
const p = this.popover;
|
|
3163
3317
|
if (!p) return;
|
|
@@ -3167,6 +3321,7 @@ var Calendar = class {
|
|
|
3167
3321
|
p.pool.place(n.id, { n, day: p.day, isStart: n.startDay >= p.day, isEnd: n.endDay <= p.day + 1, variant: n.allDay ? "bar" : "dot", mirror: false });
|
|
3168
3322
|
}
|
|
3169
3323
|
p.pool.end();
|
|
3324
|
+
this.positionMore();
|
|
3170
3325
|
}
|
|
3171
3326
|
closeMore(restoreFocus = false) {
|
|
3172
3327
|
const p = this.popover;
|
|
@@ -3194,6 +3349,7 @@ var Calendar = class {
|
|
|
3194
3349
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
3195
3350
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
3196
3351
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3352
|
+
this.listenMoreGeometry(true);
|
|
3197
3353
|
}
|
|
3198
3354
|
unbindEvents() {
|
|
3199
3355
|
const r = this.root;
|
|
@@ -3212,6 +3368,7 @@ var Calendar = class {
|
|
|
3212
3368
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
3213
3369
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
3214
3370
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3371
|
+
this.listenMoreGeometry(false);
|
|
3215
3372
|
this.listenDoc(false);
|
|
3216
3373
|
}
|
|
3217
3374
|
listenDoc(on) {
|
|
@@ -3257,6 +3414,7 @@ var Calendar = class {
|
|
|
3257
3414
|
mode = "select";
|
|
3258
3415
|
}
|
|
3259
3416
|
const touch = ev.pointerType === "touch";
|
|
3417
|
+
const overflowOrigin = itemEl?.closest(".tnc-popover") && origin ? wallTime(origin.startMs, this.zone) : null;
|
|
3260
3418
|
const s = {
|
|
3261
3419
|
mode,
|
|
3262
3420
|
pointerId: ev.pointerId,
|
|
@@ -3265,7 +3423,7 @@ var Calendar = class {
|
|
|
3265
3423
|
y0: ev.clientY,
|
|
3266
3424
|
active: false,
|
|
3267
3425
|
origin,
|
|
3268
|
-
grab: this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
3426
|
+
grab: overflowOrigin && this.current instanceof TimeGridView && !origin?.allDay ? { kind: "time", day: overflowOrigin.day, minute: overflowOrigin.minute } : this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
3269
3427
|
proposal: null,
|
|
3270
3428
|
denied: false,
|
|
3271
3429
|
timer: null,
|
|
@@ -3768,6 +3926,25 @@ var Calendar = class {
|
|
|
3768
3926
|
if (el && meta) this.opts.onItemHover?.({ ...this.itemArg(el, meta), jsEvent: ev, entering: false });
|
|
3769
3927
|
}
|
|
3770
3928
|
onKeyDown = (ev) => {
|
|
3929
|
+
if (this.popover && !ev.altKey && !ev.ctrlKey && !ev.metaKey && ["ArrowDown", "ArrowUp", "Home", "End"].includes(ev.key)) {
|
|
3930
|
+
const rows = [...this.popover.el.querySelectorAll(".tnc-item")];
|
|
3931
|
+
const index = rows.indexOf(this.doc.activeElement);
|
|
3932
|
+
const next = ev.key === "Home" ? 0 : ev.key === "End" ? rows.length - 1 : ev.key === "ArrowDown" ? (index + 1) % rows.length : index <= 0 ? rows.length - 1 : index - 1;
|
|
3933
|
+
ev.preventDefault();
|
|
3934
|
+
ev.stopPropagation();
|
|
3935
|
+
rows[next]?.focus();
|
|
3936
|
+
rows[next]?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
3937
|
+
return;
|
|
3938
|
+
}
|
|
3939
|
+
if (ev.key === "Tab" && this.popover) {
|
|
3940
|
+
const controls = [...this.popover.el.querySelectorAll('button, a[href], [tabindex="0"]')];
|
|
3941
|
+
const index = controls.indexOf(this.doc.activeElement);
|
|
3942
|
+
const next = ev.shiftKey ? index <= 0 ? controls.length - 1 : index - 1 : (index + 1) % controls.length;
|
|
3943
|
+
ev.preventDefault();
|
|
3944
|
+
ev.stopPropagation();
|
|
3945
|
+
controls[next]?.focus();
|
|
3946
|
+
return;
|
|
3947
|
+
}
|
|
3771
3948
|
if (ev.key === "Escape" && this.popover) {
|
|
3772
3949
|
ev.preventDefault();
|
|
3773
3950
|
ev.stopPropagation();
|
package/dist/esm/core.js
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
TIMELINE_UNIT_DAYS,
|
|
8
8
|
defaultOptions,
|
|
9
9
|
localeFirstDay
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-EDRCVOY7.js";
|
|
11
11
|
import {
|
|
12
12
|
DAY_MS,
|
|
13
13
|
ItemStore,
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
wallTime,
|
|
50
50
|
weekdayOf,
|
|
51
51
|
ymdOfDay
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-66A6EMCU.js";
|
|
53
53
|
|
|
54
54
|
// src/tasknotes/bases.ts
|
|
55
55
|
var FROM_BASES = {
|
package/dist/esm/react.js
CHANGED
|
@@ -19,6 +19,11 @@ export interface TimedPlacement<D = unknown> extends TimedSegment<D> {
|
|
|
19
19
|
span: number;
|
|
20
20
|
/** Index of the overlap cluster within the day. */
|
|
21
21
|
cluster: number;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the cluster overlaps an item in the other lane (plan vs actual).
|
|
24
|
+
* Only then does the item keep to its lane's share of the column.
|
|
25
|
+
*/
|
|
26
|
+
split: boolean;
|
|
22
27
|
}
|
|
23
28
|
export interface TimedDayLayout<D = unknown> {
|
|
24
29
|
day: Day;
|
|
@@ -19,6 +19,11 @@ export interface TimedPlacement<D = unknown> extends TimedSegment<D> {
|
|
|
19
19
|
span: number;
|
|
20
20
|
/** Index of the overlap cluster within the day. */
|
|
21
21
|
cluster: number;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the cluster overlaps an item in the other lane (plan vs actual).
|
|
24
|
+
* Only then does the item keep to its lane's share of the column.
|
|
25
|
+
*/
|
|
26
|
+
split: boolean;
|
|
22
27
|
}
|
|
23
28
|
export interface TimedDayLayout<D = unknown> {
|
|
24
29
|
day: Day;
|
|
@@ -144,7 +144,7 @@ export declare class Calendar<D = unknown> implements ViewContext<D> {
|
|
|
144
144
|
private selectedSet;
|
|
145
145
|
private applySelected;
|
|
146
146
|
destroyItemEl(el: HTMLElement): void;
|
|
147
|
-
openMore(day: Day, anchor: HTMLElement, jsEvent: MouseEvent | KeyboardEvent): void;
|
|
147
|
+
openMore(day: Day, anchor: HTMLElement, jsEvent: MouseEvent | KeyboardEvent, itemIds?: readonly string[]): void;
|
|
148
148
|
private resolveText;
|
|
149
149
|
private todayDay;
|
|
150
150
|
private toDay;
|
|
@@ -170,6 +170,7 @@ export declare class Calendar<D = unknown> implements ViewContext<D> {
|
|
|
170
170
|
dateClick(day: Day, jsEvent: MouseEvent | KeyboardEvent): void;
|
|
171
171
|
private createView;
|
|
172
172
|
private renderToolbar;
|
|
173
|
+
private syncToolbarViewDisplay;
|
|
173
174
|
private applyChange;
|
|
174
175
|
/** Renders pending changes now. Called automatically on the next frame. */
|
|
175
176
|
flush(): void;
|
|
@@ -184,6 +185,9 @@ export declare class Calendar<D = unknown> implements ViewContext<D> {
|
|
|
184
185
|
private addHandles;
|
|
185
186
|
/** Start and finish link handles (a milestone has one). */
|
|
186
187
|
private addPorts;
|
|
188
|
+
private readonly onMoreGeometryChange;
|
|
189
|
+
private listenMoreGeometry;
|
|
190
|
+
private positionMore;
|
|
187
191
|
private refreshMore;
|
|
188
192
|
private closeMore;
|
|
189
193
|
private bindEvents;
|
|
@@ -144,7 +144,7 @@ export declare class Calendar<D = unknown> implements ViewContext<D> {
|
|
|
144
144
|
private selectedSet;
|
|
145
145
|
private applySelected;
|
|
146
146
|
destroyItemEl(el: HTMLElement): void;
|
|
147
|
-
openMore(day: Day, anchor: HTMLElement, jsEvent: MouseEvent | KeyboardEvent): void;
|
|
147
|
+
openMore(day: Day, anchor: HTMLElement, jsEvent: MouseEvent | KeyboardEvent, itemIds?: readonly string[]): void;
|
|
148
148
|
private resolveText;
|
|
149
149
|
private todayDay;
|
|
150
150
|
private toDay;
|
|
@@ -170,6 +170,7 @@ export declare class Calendar<D = unknown> implements ViewContext<D> {
|
|
|
170
170
|
dateClick(day: Day, jsEvent: MouseEvent | KeyboardEvent): void;
|
|
171
171
|
private createView;
|
|
172
172
|
private renderToolbar;
|
|
173
|
+
private syncToolbarViewDisplay;
|
|
173
174
|
private applyChange;
|
|
174
175
|
/** Renders pending changes now. Called automatically on the next frame. */
|
|
175
176
|
flush(): void;
|
|
@@ -184,6 +185,9 @@ export declare class Calendar<D = unknown> implements ViewContext<D> {
|
|
|
184
185
|
private addHandles;
|
|
185
186
|
/** Start and finish link handles (a milestone has one). */
|
|
186
187
|
private addPorts;
|
|
188
|
+
private readonly onMoreGeometryChange;
|
|
189
|
+
private listenMoreGeometry;
|
|
190
|
+
private positionMore;
|
|
187
191
|
private refreshMore;
|
|
188
192
|
private closeMore;
|
|
189
193
|
private bindEvents;
|
|
@@ -61,7 +61,7 @@ export interface ViewContext<D = unknown> {
|
|
|
61
61
|
destroyItemEl(el: HTMLElement): void;
|
|
62
62
|
/** Builds a nav link (day number / header) for a day. */
|
|
63
63
|
navLink(day: Day, label: Node | string, cls: string): HTMLElement;
|
|
64
|
-
openMore(day: Day, anchor: HTMLElement, jsEvent: MouseEvent | KeyboardEvent): void;
|
|
64
|
+
openMore(day: Day, anchor: HTMLElement, jsEvent: MouseEvent | KeyboardEvent, itemIds?: readonly string[]): void;
|
|
65
65
|
/** Follows a date link: `onNavLink`, or switches to `view` at `day`. */
|
|
66
66
|
navigate(day: Day, jsEvent: MouseEvent | KeyboardEvent, view?: "day" | "month"): void;
|
|
67
67
|
/** Swallows the click that follows a drag the view handled itself. */
|