@tasknotes/calendar 0.1.6 → 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 +16 -0
- package/README.md +31 -0
- package/dist/calendar.css +53 -0
- package/dist/cjs/index.cjs +190 -30
- package/dist/cjs/react.cjs +190 -30
- package/dist/esm/{chunk-URE7QLU3.js → chunk-EDRCVOY7.js} +190 -30
- package/dist/esm/index.js +1 -1
- package/dist/esm/react.js +1 -1
- 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
|
@@ -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();
|
|
@@ -1109,6 +1121,8 @@ var TimeGridView = class {
|
|
|
1109
1121
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1110
1122
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1111
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());
|
|
1112
1126
|
this.top = h(doc, "div", "tnc-tg-top");
|
|
1113
1127
|
this.headRow = h(doc, "div", "tnc-tg-head tnc-tg-cols", { role: "row" });
|
|
1114
1128
|
this.top.append(this.headRow);
|
|
@@ -1136,7 +1150,7 @@ var TimeGridView = class {
|
|
|
1136
1150
|
} else {
|
|
1137
1151
|
this.body = this.gutter = null;
|
|
1138
1152
|
}
|
|
1139
|
-
this.el.append(this.scroller);
|
|
1153
|
+
this.el.append(this.dayNavigation, this.scroller);
|
|
1140
1154
|
this.build();
|
|
1141
1155
|
if (showGrid && opts.height === "fill") {
|
|
1142
1156
|
const RO = ctx.win.ResizeObserver;
|
|
@@ -1149,6 +1163,7 @@ var TimeGridView = class {
|
|
|
1149
1163
|
ctx;
|
|
1150
1164
|
el;
|
|
1151
1165
|
scroller;
|
|
1166
|
+
dayNavigation;
|
|
1152
1167
|
top;
|
|
1153
1168
|
headRow;
|
|
1154
1169
|
allDayRow;
|
|
@@ -1204,6 +1219,9 @@ var TimeGridView = class {
|
|
|
1204
1219
|
head.append(cap);
|
|
1205
1220
|
}
|
|
1206
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);
|
|
1207
1225
|
opts.dayHeaderDidMount?.({ ...arg, el: head });
|
|
1208
1226
|
const col = h(doc, "div", ["tnc-col", ...ctx.dayClasses(day)].join(" "), { role: "cell", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr });
|
|
1209
1227
|
const bgLayer = h(doc, "div", "tnc-col-bg");
|
|
@@ -1220,6 +1238,7 @@ var TimeGridView = class {
|
|
|
1220
1238
|
this.body?.append(col);
|
|
1221
1239
|
return {
|
|
1222
1240
|
day,
|
|
1241
|
+
collisionColumns: 1,
|
|
1223
1242
|
head,
|
|
1224
1243
|
col,
|
|
1225
1244
|
plan: new ItemPool(ctx, planLayer),
|
|
@@ -1276,12 +1295,17 @@ var TimeGridView = class {
|
|
|
1276
1295
|
/* --------------------------------------------------------------- render */
|
|
1277
1296
|
render(change) {
|
|
1278
1297
|
this.renderAllDay();
|
|
1279
|
-
if (!this.body)
|
|
1298
|
+
if (!this.body) {
|
|
1299
|
+
this.updateDayNavigation();
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1280
1302
|
const dirty = change ? change.days : null;
|
|
1281
1303
|
for (const c of this.cols) {
|
|
1282
1304
|
if (dirty && !dirty.has(c.day) && !(this.ctx.slot.max > MINUTES_PER_DAY && dirty.has(c.day + 1))) continue;
|
|
1283
1305
|
this.renderColumn(c);
|
|
1284
1306
|
}
|
|
1307
|
+
this.el.style.setProperty("--tnc-collision-columns", String(Math.max(1, ...this.cols.map((column) => column.collisionColumns))));
|
|
1308
|
+
this.updateDayNavigation();
|
|
1285
1309
|
this.renderCapacity();
|
|
1286
1310
|
this.tick();
|
|
1287
1311
|
}
|
|
@@ -1399,6 +1423,11 @@ var TimeGridView = class {
|
|
|
1399
1423
|
}
|
|
1400
1424
|
const minMinutes = opts.eventMinHeight / this.ppm;
|
|
1401
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
|
+
);
|
|
1402
1431
|
const total = this.minutes;
|
|
1403
1432
|
const top = (m) => pct((m - slot.min) / total);
|
|
1404
1433
|
const height = (s) => pct((s.endMin - s.startMin) / total);
|
|
@@ -1410,14 +1439,22 @@ var TimeGridView = class {
|
|
|
1410
1439
|
c.bg.end();
|
|
1411
1440
|
const hasActual = layout.actual.length > 0;
|
|
1412
1441
|
c.col.classList.toggle("has-actual", hasActual);
|
|
1442
|
+
for (const link of c.more.children) link.setAttribute("data-stale", "true");
|
|
1413
1443
|
this.placeLane(c.plan, layout.plan, c, top, height, "plan");
|
|
1414
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();
|
|
1415
1446
|
}
|
|
1416
1447
|
placeLane(pool, placements, c, top, height, lane) {
|
|
1417
1448
|
const { ctx } = this;
|
|
1418
1449
|
const { opts } = ctx;
|
|
1419
|
-
const
|
|
1420
|
-
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;
|
|
1421
1458
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
1422
1459
|
pool.begin();
|
|
1423
1460
|
for (const p of placements) {
|
|
@@ -1443,21 +1480,38 @@ var TimeGridView = class {
|
|
|
1443
1480
|
top: top(p.startMin),
|
|
1444
1481
|
height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
1445
1482
|
...inLane(lane, p.split, left, width),
|
|
1483
|
+
"--tnc-event-readable-width": pct(span / cols / width),
|
|
1446
1484
|
"z-index": String(p.col + 1)
|
|
1447
1485
|
});
|
|
1448
1486
|
}
|
|
1449
1487
|
pool.end();
|
|
1450
1488
|
if (!withMore) return;
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
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
|
+
}
|
|
1454
1510
|
const cols = maxStack;
|
|
1455
1511
|
setStyle(link, {
|
|
1456
1512
|
top: top(rec.start),
|
|
1457
1513
|
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
1458
1514
|
});
|
|
1459
|
-
link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
|
|
1460
|
-
c.more.append(link);
|
|
1461
1515
|
}
|
|
1462
1516
|
}
|
|
1463
1517
|
meta(s, bg) {
|
|
@@ -1568,6 +1622,26 @@ var TimeGridView = class {
|
|
|
1568
1622
|
}
|
|
1569
1623
|
resize() {
|
|
1570
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
|
+
});
|
|
1571
1645
|
}
|
|
1572
1646
|
scrollToTime(minute) {
|
|
1573
1647
|
if (!this.body) return;
|
|
@@ -2333,7 +2407,11 @@ var Calendar = class {
|
|
|
2333
2407
|
this.resizeObs?.disconnect();
|
|
2334
2408
|
const RO = this.win.ResizeObserver;
|
|
2335
2409
|
if (RO) {
|
|
2336
|
-
this.resizeObs = new RO(() =>
|
|
2410
|
+
this.resizeObs = new RO(() => {
|
|
2411
|
+
this.syncToolbarViewDisplay();
|
|
2412
|
+
this.current?.resize?.();
|
|
2413
|
+
this.positionMore();
|
|
2414
|
+
});
|
|
2337
2415
|
this.resizeObs.observe(this.root);
|
|
2338
2416
|
}
|
|
2339
2417
|
}
|
|
@@ -2353,9 +2431,11 @@ var Calendar = class {
|
|
|
2353
2431
|
if (this.frame !== null) old.cancelAnimationFrame(this.frame);
|
|
2354
2432
|
this.timer = this.frame = null;
|
|
2355
2433
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
2434
|
+
this.listenMoreGeometry(false);
|
|
2356
2435
|
this.doc = doc;
|
|
2357
2436
|
this.win = doc.defaultView ?? old;
|
|
2358
2437
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
2438
|
+
this.listenMoreGeometry(true);
|
|
2359
2439
|
this.observeResize();
|
|
2360
2440
|
this.rebuild(false);
|
|
2361
2441
|
this.scheduleTick();
|
|
@@ -2473,6 +2553,7 @@ var Calendar = class {
|
|
|
2473
2553
|
goToday() {
|
|
2474
2554
|
this.anchor = this.todayDay();
|
|
2475
2555
|
this.rebuild(true);
|
|
2556
|
+
if (this.current instanceof TimeGridView) this.current.revealDay(this.anchor);
|
|
2476
2557
|
}
|
|
2477
2558
|
/** The anchor date, as local midnight of that civil day in the calendar zone. */
|
|
2478
2559
|
getDate() {
|
|
@@ -2491,6 +2572,7 @@ var Calendar = class {
|
|
|
2491
2572
|
/** Re-measures (e.g. after the host was shown or resized by a parent). */
|
|
2492
2573
|
updateSize() {
|
|
2493
2574
|
this.current?.resize?.();
|
|
2575
|
+
this.positionMore();
|
|
2494
2576
|
}
|
|
2495
2577
|
getScroll() {
|
|
2496
2578
|
return this.current?.getScroll() ?? { top: 0, left: 0 };
|
|
@@ -2522,7 +2604,10 @@ var Calendar = class {
|
|
|
2522
2604
|
}
|
|
2523
2605
|
const view = this.current;
|
|
2524
2606
|
if (view instanceof TimelineView) view.scrollToInstant(ms, opts.align ?? "center", opts.smooth ?? false);
|
|
2525
|
-
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
|
+
}
|
|
2526
2611
|
}
|
|
2527
2612
|
/** What is under a client point: a time slot, a day, or a timeline row and date. Null outside the view. */
|
|
2528
2613
|
hitTest(clientX, clientY) {
|
|
@@ -2697,9 +2782,10 @@ var Calendar = class {
|
|
|
2697
2782
|
if (meta) this.opts.itemWillUnmount?.(this.itemArg(el, meta));
|
|
2698
2783
|
el.remove();
|
|
2699
2784
|
}
|
|
2700
|
-
openMore(day, anchor, jsEvent) {
|
|
2785
|
+
openMore(day, anchor, jsEvent, itemIds) {
|
|
2701
2786
|
jsEvent.stopPropagation();
|
|
2702
|
-
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));
|
|
2703
2789
|
const shown = new Set(
|
|
2704
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 ?? "")
|
|
2705
2791
|
);
|
|
@@ -2728,15 +2814,7 @@ var Calendar = class {
|
|
|
2728
2814
|
}
|
|
2729
2815
|
pool.end();
|
|
2730
2816
|
this.popover = { el, pool, anchor, day };
|
|
2731
|
-
|
|
2732
|
-
const ar = anchor.getBoundingClientRect();
|
|
2733
|
-
const w = Math.max(220, ar.width + 24);
|
|
2734
|
-
let left = ar.left - rr.left - 12;
|
|
2735
|
-
left = Math.max(4, Math.min(left, rr.width - w - 4));
|
|
2736
|
-
let top = ar.top - rr.top - 40;
|
|
2737
|
-
const height = el.offsetHeight;
|
|
2738
|
-
top = Math.max(4, Math.min(top, rr.height - height - 4));
|
|
2739
|
-
setStyle(el, { left, top, width: w });
|
|
2817
|
+
this.positionMore();
|
|
2740
2818
|
el.focus();
|
|
2741
2819
|
}
|
|
2742
2820
|
/* =============================================================== render */
|
|
@@ -2860,8 +2938,10 @@ var Calendar = class {
|
|
|
2860
2938
|
this.current.setScroll({ top: keep.top, left: 0 });
|
|
2861
2939
|
this.current.placeInstant(keep.ms, keep.vx);
|
|
2862
2940
|
} else if (scroll) this.current.setScroll(scroll);
|
|
2863
|
-
else if (this.current instanceof TimeGridView)
|
|
2864
|
-
|
|
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);
|
|
2865
2945
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
2866
2946
|
}
|
|
2867
2947
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -3032,17 +3112,42 @@ var Calendar = class {
|
|
|
3032
3112
|
);
|
|
3033
3113
|
const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
|
|
3034
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
|
+
});
|
|
3035
3118
|
for (const v of views) {
|
|
3036
3119
|
const label = v === "days" ? this.text.views.days ?? `${this.opts.dayCount} days` : this.text.views[v] ?? v;
|
|
3037
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));
|
|
3038
3122
|
}
|
|
3123
|
+
viewSelect.value = this.opts.view;
|
|
3124
|
+
viewSelect.addEventListener("change", () => this.changeView(viewSelect.value));
|
|
3039
3125
|
const start = h(this.doc, "div", "tnc-toolbar-start");
|
|
3040
3126
|
const end = h(this.doc, "div", "tnc-toolbar-end");
|
|
3041
3127
|
if (cfg.extraPlacement === "start") start.append(...extras);
|
|
3042
3128
|
start.append(nav, title);
|
|
3043
|
-
end.append(viewGroup);
|
|
3129
|
+
end.append(viewGroup, viewSelect);
|
|
3044
3130
|
if (cfg.extraPlacement !== "start") end.append(...extras);
|
|
3045
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";
|
|
3046
3151
|
}
|
|
3047
3152
|
applyChange(change) {
|
|
3048
3153
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3175,6 +3280,38 @@ var Calendar = class {
|
|
|
3175
3280
|
}
|
|
3176
3281
|
}
|
|
3177
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
|
+
}
|
|
3178
3315
|
refreshMore() {
|
|
3179
3316
|
const p = this.popover;
|
|
3180
3317
|
if (!p) return;
|
|
@@ -3184,6 +3321,7 @@ var Calendar = class {
|
|
|
3184
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 });
|
|
3185
3322
|
}
|
|
3186
3323
|
p.pool.end();
|
|
3324
|
+
this.positionMore();
|
|
3187
3325
|
}
|
|
3188
3326
|
closeMore(restoreFocus = false) {
|
|
3189
3327
|
const p = this.popover;
|
|
@@ -3211,6 +3349,7 @@ var Calendar = class {
|
|
|
3211
3349
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
3212
3350
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
3213
3351
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3352
|
+
this.listenMoreGeometry(true);
|
|
3214
3353
|
}
|
|
3215
3354
|
unbindEvents() {
|
|
3216
3355
|
const r = this.root;
|
|
@@ -3229,6 +3368,7 @@ var Calendar = class {
|
|
|
3229
3368
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
3230
3369
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
3231
3370
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3371
|
+
this.listenMoreGeometry(false);
|
|
3232
3372
|
this.listenDoc(false);
|
|
3233
3373
|
}
|
|
3234
3374
|
listenDoc(on) {
|
|
@@ -3274,6 +3414,7 @@ var Calendar = class {
|
|
|
3274
3414
|
mode = "select";
|
|
3275
3415
|
}
|
|
3276
3416
|
const touch = ev.pointerType === "touch";
|
|
3417
|
+
const overflowOrigin = itemEl?.closest(".tnc-popover") && origin ? wallTime(origin.startMs, this.zone) : null;
|
|
3277
3418
|
const s = {
|
|
3278
3419
|
mode,
|
|
3279
3420
|
pointerId: ev.pointerId,
|
|
@@ -3282,7 +3423,7 @@ var Calendar = class {
|
|
|
3282
3423
|
y0: ev.clientY,
|
|
3283
3424
|
active: false,
|
|
3284
3425
|
origin,
|
|
3285
|
-
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,
|
|
3286
3427
|
proposal: null,
|
|
3287
3428
|
denied: false,
|
|
3288
3429
|
timer: null,
|
|
@@ -3785,6 +3926,25 @@ var Calendar = class {
|
|
|
3785
3926
|
if (el && meta) this.opts.onItemHover?.({ ...this.itemArg(el, meta), jsEvent: ev, entering: false });
|
|
3786
3927
|
}
|
|
3787
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
|
+
}
|
|
3788
3948
|
if (ev.key === "Escape" && this.popover) {
|
|
3789
3949
|
ev.preventDefault();
|
|
3790
3950
|
ev.stopPropagation();
|
package/dist/esm/index.js
CHANGED
package/dist/esm/react.js
CHANGED
|
@@ -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. */
|
|
@@ -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. */
|
|
@@ -258,6 +258,7 @@ export interface ToolbarButton {
|
|
|
258
258
|
export interface ToolbarOptions {
|
|
259
259
|
/** View buttons, in order. */
|
|
260
260
|
views?: ViewType[];
|
|
261
|
+
viewDisplay?: "auto" | "buttons" | "select";
|
|
261
262
|
extraButtons?: ToolbarButton[];
|
|
262
263
|
/** Extra buttons go before prev/next ("start") or after the view buttons ("end"). */
|
|
263
264
|
extraPlacement?: "start" | "end";
|
|
@@ -269,6 +270,7 @@ export interface CalendarText {
|
|
|
269
270
|
allDay: string;
|
|
270
271
|
noItems: string;
|
|
271
272
|
more: (n: number) => string;
|
|
273
|
+
appointments: (n: number) => string;
|
|
272
274
|
/** Day-cell label suffix in `monthDisplay: "dots"`. */
|
|
273
275
|
itemCount: (n: number) => string;
|
|
274
276
|
weekNumber: (n: number) => string;
|
|
@@ -319,6 +321,7 @@ export interface CalendarOptions<D = unknown> {
|
|
|
319
321
|
}) => void;
|
|
320
322
|
/** Agenda lists: an add row with this label under every day, reported through `onListAdd`. Null for none. */
|
|
321
323
|
listAddText: string | null;
|
|
324
|
+
listEmptyContent?: () => Node;
|
|
322
325
|
onListAdd?: (arg: DayArg & {
|
|
323
326
|
jsEvent: MouseEvent | KeyboardEvent;
|
|
324
327
|
}) => void;
|
|
@@ -258,6 +258,7 @@ export interface ToolbarButton {
|
|
|
258
258
|
export interface ToolbarOptions {
|
|
259
259
|
/** View buttons, in order. */
|
|
260
260
|
views?: ViewType[];
|
|
261
|
+
viewDisplay?: "auto" | "buttons" | "select";
|
|
261
262
|
extraButtons?: ToolbarButton[];
|
|
262
263
|
/** Extra buttons go before prev/next ("start") or after the view buttons ("end"). */
|
|
263
264
|
extraPlacement?: "start" | "end";
|
|
@@ -269,6 +270,7 @@ export interface CalendarText {
|
|
|
269
270
|
allDay: string;
|
|
270
271
|
noItems: string;
|
|
271
272
|
more: (n: number) => string;
|
|
273
|
+
appointments: (n: number) => string;
|
|
272
274
|
/** Day-cell label suffix in `monthDisplay: "dots"`. */
|
|
273
275
|
itemCount: (n: number) => string;
|
|
274
276
|
weekNumber: (n: number) => string;
|
|
@@ -319,6 +321,7 @@ export interface CalendarOptions<D = unknown> {
|
|
|
319
321
|
}) => void;
|
|
320
322
|
/** Agenda lists: an add row with this label under every day, reported through `onListAdd`. Null for none. */
|
|
321
323
|
listAddText: string | null;
|
|
324
|
+
listEmptyContent?: () => Node;
|
|
322
325
|
onListAdd?: (arg: DayArg & {
|
|
323
326
|
jsEvent: MouseEvent | KeyboardEvent;
|
|
324
327
|
}) => void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Day } from "../../core/date.cjs";
|
|
1
2
|
import type { StoreChange } from "../../core/store.cjs";
|
|
2
3
|
import { type Selection, type View, type ViewContext } from "../context.cjs";
|
|
3
4
|
import type { Hit } from "../options.cjs";
|
|
@@ -6,6 +7,7 @@ export declare class TimeGridView<D> implements View {
|
|
|
6
7
|
private readonly ctx;
|
|
7
8
|
readonly el: HTMLElement;
|
|
8
9
|
private readonly scroller;
|
|
10
|
+
private readonly dayNavigation;
|
|
9
11
|
private readonly top;
|
|
10
12
|
private readonly headRow;
|
|
11
13
|
private readonly allDayRow;
|
|
@@ -40,6 +42,8 @@ export declare class TimeGridView<D> implements View {
|
|
|
40
42
|
hit(x: number, y: number): Hit | null;
|
|
41
43
|
tick(): void;
|
|
42
44
|
resize(): void;
|
|
45
|
+
revealDay(day: Day): void;
|
|
46
|
+
private updateDayNavigation;
|
|
43
47
|
scrollToTime(minute: number): void;
|
|
44
48
|
getScroll(): {
|
|
45
49
|
top: number;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Day } from "../../core/date.js";
|
|
1
2
|
import type { StoreChange } from "../../core/store.js";
|
|
2
3
|
import { type Selection, type View, type ViewContext } from "../context.js";
|
|
3
4
|
import type { Hit } from "../options.js";
|
|
@@ -6,6 +7,7 @@ export declare class TimeGridView<D> implements View {
|
|
|
6
7
|
private readonly ctx;
|
|
7
8
|
readonly el: HTMLElement;
|
|
8
9
|
private readonly scroller;
|
|
10
|
+
private readonly dayNavigation;
|
|
9
11
|
private readonly top;
|
|
10
12
|
private readonly headRow;
|
|
11
13
|
private readonly allDayRow;
|
|
@@ -40,6 +42,8 @@ export declare class TimeGridView<D> implements View {
|
|
|
40
42
|
hit(x: number, y: number): Hit | null;
|
|
41
43
|
tick(): void;
|
|
42
44
|
resize(): void;
|
|
45
|
+
revealDay(day: Day): void;
|
|
46
|
+
private updateDayNavigation;
|
|
43
47
|
scrollToTime(minute: number): void;
|
|
44
48
|
getScroll(): {
|
|
45
49
|
top: number;
|
package/package.json
CHANGED