@tasknotes/calendar 0.1.2 → 0.1.3
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 +10 -0
- package/README.md +2 -1
- package/dist/calendar.css +196 -0
- package/dist/cjs/index.cjs +358 -29
- package/dist/cjs/react.cjs +358 -29
- package/dist/esm/{chunk-ZJQMMA6W.js → chunk-XHPZ7K4W.js} +358 -29
- package/dist/esm/index.js +1 -1
- package/dist/esm/react.js +1 -1
- package/dist/types/dom/calendar.d.cts +8 -0
- package/dist/types/dom/calendar.d.ts +8 -0
- package/dist/types/dom/context.d.cts +8 -0
- package/dist/types/dom/context.d.ts +8 -0
- package/dist/types/dom/options.d.cts +19 -0
- package/dist/types/dom/options.d.ts +19 -0
- package/dist/types/dom/views/list.d.cts +23 -2
- package/dist/types/dom/views/list.d.ts +23 -2
- package/dist/types/dom/views/strip.d.cts +43 -0
- package/dist/types/dom/views/strip.d.ts +43 -0
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -948,6 +948,7 @@ function defaultOptions() {
|
|
|
948
948
|
dayCount: 3,
|
|
949
949
|
listDayCount: 7,
|
|
950
950
|
listStart: "date",
|
|
951
|
+
listAgenda: false,
|
|
951
952
|
monthDisplay: "full",
|
|
952
953
|
timeZone: "local",
|
|
953
954
|
locale: void 0,
|
|
@@ -997,6 +998,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
997
998
|
"dayCount",
|
|
998
999
|
"listDayCount",
|
|
999
1000
|
"listStart",
|
|
1001
|
+
"listAgenda",
|
|
1000
1002
|
"monthDisplay",
|
|
1001
1003
|
"timeZone",
|
|
1002
1004
|
"locale",
|
|
@@ -1338,15 +1340,225 @@ var DayGridView = class {
|
|
|
1338
1340
|
}
|
|
1339
1341
|
};
|
|
1340
1342
|
|
|
1343
|
+
// src/dom/views/strip.ts
|
|
1344
|
+
var SWIPE_PX = 40;
|
|
1345
|
+
var DateStrip = class {
|
|
1346
|
+
constructor(ctx, pick, focus) {
|
|
1347
|
+
this.ctx = ctx;
|
|
1348
|
+
this.pick = pick;
|
|
1349
|
+
const { doc, text } = ctx;
|
|
1350
|
+
this.focus = focus;
|
|
1351
|
+
this.shown = startOfWeek(focus, ctx.firstDay);
|
|
1352
|
+
this.el = h(doc, "div", "tnc-strip");
|
|
1353
|
+
const head = h(doc, "div", "tnc-strip-head");
|
|
1354
|
+
this.title = h(doc, "button", "tnc-strip-title", { type: "button", "aria-expanded": "false" });
|
|
1355
|
+
this.title.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
1356
|
+
const prev = h(doc, "button", "tnc-strip-nav tnc-strip-prev", { type: "button", "aria-label": text.prev, title: text.prev }, "\u2039");
|
|
1357
|
+
const next = h(doc, "button", "tnc-strip-nav tnc-strip-next", { type: "button", "aria-label": text.next, title: text.next }, "\u203A");
|
|
1358
|
+
prev.addEventListener("click", () => this.step(-1));
|
|
1359
|
+
next.addEventListener("click", () => this.step(1));
|
|
1360
|
+
this.slot = h(doc, "div", "tnc-strip-slot", { "data-tnc-no-drag": "" });
|
|
1361
|
+
head.append(this.title, prev, next, this.slot);
|
|
1362
|
+
this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
|
|
1363
|
+
this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
|
|
1364
|
+
const handle = h(doc, "button", "tnc-strip-handle", { type: "button", tabindex: "-1", "aria-hidden": "true" });
|
|
1365
|
+
handle.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
1366
|
+
this.el.append(head, this.dows, this.grid, handle);
|
|
1367
|
+
this.grid.addEventListener("click", this.onClick);
|
|
1368
|
+
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
1369
|
+
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
1370
|
+
this.el.addEventListener("pointerup", this.onPointerUp);
|
|
1371
|
+
this.el.addEventListener("pointercancel", this.onPointerCancel);
|
|
1372
|
+
this.el.addEventListener("click", this.onClickCapture, true);
|
|
1373
|
+
this.draw();
|
|
1374
|
+
ctx.opts.stripSlotDidMount?.({ el: this.slot });
|
|
1375
|
+
}
|
|
1376
|
+
ctx;
|
|
1377
|
+
pick;
|
|
1378
|
+
el;
|
|
1379
|
+
title;
|
|
1380
|
+
dows;
|
|
1381
|
+
grid;
|
|
1382
|
+
slot;
|
|
1383
|
+
/** Day at the top of the list. */
|
|
1384
|
+
focus;
|
|
1385
|
+
/** First day of the week shown, or the month shown when expanded. */
|
|
1386
|
+
shown;
|
|
1387
|
+
target = null;
|
|
1388
|
+
swipe = null;
|
|
1389
|
+
swallowClick = false;
|
|
1390
|
+
expanded = false;
|
|
1391
|
+
/** The list scrolled: mark its top day and keep it in view. */
|
|
1392
|
+
setFocus(day) {
|
|
1393
|
+
if (day === this.focus) return;
|
|
1394
|
+
this.focus = day;
|
|
1395
|
+
if (!this.expanded) this.shown = startOfWeek(day, this.ctx.firstDay);
|
|
1396
|
+
else if (startOfMonth(day) !== this.shown) this.shown = startOfMonth(day);
|
|
1397
|
+
this.draw();
|
|
1398
|
+
}
|
|
1399
|
+
setExpanded(on) {
|
|
1400
|
+
if (on === this.expanded) return;
|
|
1401
|
+
this.expanded = on;
|
|
1402
|
+
this.shown = on ? startOfMonth(this.focus) : startOfWeek(this.focus, this.ctx.firstDay);
|
|
1403
|
+
this.draw();
|
|
1404
|
+
}
|
|
1405
|
+
/** Highlights the day a dragged item would land on. */
|
|
1406
|
+
markTarget(day) {
|
|
1407
|
+
if (day === this.target) return;
|
|
1408
|
+
this.target = day;
|
|
1409
|
+
for (const cell of this.grid.querySelectorAll(".tnc-strip-day"))
|
|
1410
|
+
cell.classList.toggle("is-drop-target", day !== null && cell.dataset.date === this.ctx.dayArg(day).dateStr);
|
|
1411
|
+
}
|
|
1412
|
+
dayAt(el) {
|
|
1413
|
+
const cell = el.closest(".tnc-strip-day");
|
|
1414
|
+
return cell && this.grid.contains(cell) ? parseDay(cell.dataset.date ?? "") : null;
|
|
1415
|
+
}
|
|
1416
|
+
step(dir) {
|
|
1417
|
+
this.shown = this.expanded ? addMonths(this.shown, dir) : this.shown + 7 * dir;
|
|
1418
|
+
this.draw();
|
|
1419
|
+
}
|
|
1420
|
+
days() {
|
|
1421
|
+
if (!this.expanded) return Array.from({ length: 7 }, (_, i) => this.shown + i);
|
|
1422
|
+
const first = startOfWeek(this.shown, this.ctx.firstDay);
|
|
1423
|
+
const end = addMonths(this.shown, 1);
|
|
1424
|
+
const weeks = Math.ceil((end - first) / 7);
|
|
1425
|
+
return Array.from({ length: weeks * 7 }, (_, i) => first + i);
|
|
1426
|
+
}
|
|
1427
|
+
/** Redraws the cells; also called on every list render to refresh the dots. */
|
|
1428
|
+
draw() {
|
|
1429
|
+
const { ctx } = this;
|
|
1430
|
+
const { doc, fmt, store } = ctx;
|
|
1431
|
+
const days = this.days();
|
|
1432
|
+
const titleDay = this.expanded ? this.shown : days.includes(this.focus) ? this.focus : this.shown + 3;
|
|
1433
|
+
this.title.replaceChildren(fmt.monthYear(titleDay), h(doc, "span", "tnc-strip-caret", { "aria-hidden": "true" }));
|
|
1434
|
+
this.title.setAttribute("aria-expanded", String(this.expanded));
|
|
1435
|
+
this.el.classList.toggle("is-expanded", this.expanded);
|
|
1436
|
+
this.dows.replaceChildren(...days.slice(0, 7).map((d) => h(doc, "span", null, null, fmt.weekdayNarrow(d))));
|
|
1437
|
+
const focused = this.grid.contains(doc.activeElement) ? doc.activeElement.dataset.date : void 0;
|
|
1438
|
+
const month = ymdOfDay(this.shown).month;
|
|
1439
|
+
const tabDay = days.includes(this.focus) ? this.focus : days[0];
|
|
1440
|
+
const cells = days.map((d) => {
|
|
1441
|
+
const arg = ctx.dayArg(d);
|
|
1442
|
+
const count = store.inRange(d, d + 1).filter((n) => n.item.display !== "background" && n.item.display !== "none").length;
|
|
1443
|
+
const first = ymdOfDay(d).day === 1 && d !== days[0] && !this.expanded;
|
|
1444
|
+
const label = fmt.longDate(d);
|
|
1445
|
+
const cell = h(
|
|
1446
|
+
doc,
|
|
1447
|
+
"button",
|
|
1448
|
+
[
|
|
1449
|
+
"tnc-strip-day",
|
|
1450
|
+
...ctx.dayClasses(d),
|
|
1451
|
+
d === this.focus ? "is-focus" : "",
|
|
1452
|
+
count ? "has-items" : "",
|
|
1453
|
+
this.expanded && ymdOfDay(d).month !== month ? "is-other-month" : "",
|
|
1454
|
+
d === this.target ? "is-drop-target" : ""
|
|
1455
|
+
].filter(Boolean).join(" "),
|
|
1456
|
+
{
|
|
1457
|
+
type: "button",
|
|
1458
|
+
"data-date": arg.dateStr,
|
|
1459
|
+
"aria-label": count ? `${label}, ${ctx.text.itemCount(count)}` : label,
|
|
1460
|
+
"aria-pressed": String(d === this.focus),
|
|
1461
|
+
tabindex: d === tabDay ? "0" : "-1"
|
|
1462
|
+
}
|
|
1463
|
+
);
|
|
1464
|
+
if (first) cell.append(h(doc, "span", "tnc-strip-month", { "aria-hidden": "true" }, fmt.monthShort(d)));
|
|
1465
|
+
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d)), h(doc, "span", "tnc-strip-dot", { "aria-hidden": "true" }));
|
|
1466
|
+
return cell;
|
|
1467
|
+
});
|
|
1468
|
+
this.grid.replaceChildren(...cells);
|
|
1469
|
+
this.grid.setAttribute("aria-label", fmt.monthYear(titleDay));
|
|
1470
|
+
if (focused) this.grid.querySelector(`[data-date="${focused}"]`)?.focus({ preventScroll: true });
|
|
1471
|
+
}
|
|
1472
|
+
onClick = (ev) => {
|
|
1473
|
+
const day = this.dayAt(ev.target);
|
|
1474
|
+
if (day === null) return;
|
|
1475
|
+
if (this.expanded) this.setExpanded(false);
|
|
1476
|
+
this.pick(day, ev);
|
|
1477
|
+
};
|
|
1478
|
+
onKeyDown = (ev) => {
|
|
1479
|
+
const day = this.dayAt(ev.target);
|
|
1480
|
+
if (day === null) return;
|
|
1481
|
+
const move = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[ev.key];
|
|
1482
|
+
if (move === void 0) return;
|
|
1483
|
+
ev.preventDefault();
|
|
1484
|
+
const next = day + move;
|
|
1485
|
+
const days = this.days();
|
|
1486
|
+
if (next < days[0] || next > days[days.length - 1]) {
|
|
1487
|
+
this.shown = this.expanded ? startOfMonth(next) : startOfWeek(next, this.ctx.firstDay);
|
|
1488
|
+
this.draw();
|
|
1489
|
+
}
|
|
1490
|
+
const cell = this.grid.querySelector(`[data-date="${this.ctx.dayArg(next).dateStr}"]`);
|
|
1491
|
+
if (!cell) return;
|
|
1492
|
+
for (const c of this.grid.querySelectorAll("[tabindex='0']")) c.setAttribute("tabindex", "-1");
|
|
1493
|
+
cell.setAttribute("tabindex", "0");
|
|
1494
|
+
cell.focus();
|
|
1495
|
+
};
|
|
1496
|
+
onPointerDown = (ev) => {
|
|
1497
|
+
if (ev.button !== 0 || this.ctx.draggingId) return;
|
|
1498
|
+
this.swipe = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
|
|
1499
|
+
};
|
|
1500
|
+
onPointerUp = (ev) => {
|
|
1501
|
+
const s = this.swipe;
|
|
1502
|
+
this.swipe = null;
|
|
1503
|
+
if (!s || s.id !== ev.pointerId) return;
|
|
1504
|
+
const dx = ev.clientX - s.x;
|
|
1505
|
+
if (Math.abs(dx) < SWIPE_PX || Math.abs(dx) < Math.abs(ev.clientY - s.y)) return;
|
|
1506
|
+
this.swallowClick = true;
|
|
1507
|
+
this.step(dx < 0 ? 1 : -1);
|
|
1508
|
+
};
|
|
1509
|
+
onPointerCancel = () => {
|
|
1510
|
+
this.swipe = null;
|
|
1511
|
+
};
|
|
1512
|
+
/** A swipe that ends on a day is not a pick. */
|
|
1513
|
+
onClickCapture = (ev) => {
|
|
1514
|
+
if (!this.swallowClick) return;
|
|
1515
|
+
this.swallowClick = false;
|
|
1516
|
+
ev.stopPropagation();
|
|
1517
|
+
ev.preventDefault();
|
|
1518
|
+
};
|
|
1519
|
+
destroy() {
|
|
1520
|
+
this.ctx.opts.stripSlotWillUnmount?.({ el: this.slot });
|
|
1521
|
+
this.el.remove();
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1341
1525
|
// src/dom/views/list.ts
|
|
1526
|
+
var LOAD_AHEAD = "0px 0px 600px 0px";
|
|
1342
1527
|
var ListView = class {
|
|
1343
1528
|
constructor(ctx) {
|
|
1344
1529
|
this.ctx = ctx;
|
|
1345
|
-
|
|
1346
|
-
this.
|
|
1347
|
-
this.
|
|
1530
|
+
const { doc, win, opts } = ctx;
|
|
1531
|
+
this.el = h(doc, "div", "tnc-list");
|
|
1532
|
+
this.scroller = h(doc, "div", "tnc-list-scroll");
|
|
1533
|
+
this.body = h(doc, "div", "tnc-list-body", { role: "list", "aria-label": ctx.view.title });
|
|
1348
1534
|
this.scroller.append(this.body);
|
|
1349
1535
|
this.el.append(this.scroller);
|
|
1536
|
+
if (!opts.listAgenda) return;
|
|
1537
|
+
this.el.classList.add("tnc-list--agenda");
|
|
1538
|
+
this.strip = new DateStrip(ctx, (day, ev) => this.pick(day, ev), ctx.view.range.startDay);
|
|
1539
|
+
this.el.prepend(this.strip.el);
|
|
1540
|
+
this.sentinel = h(doc, "div", "tnc-list-more", { "aria-hidden": "true" });
|
|
1541
|
+
this.scroller.append(this.sentinel);
|
|
1542
|
+
const globals = win;
|
|
1543
|
+
const IO = globals.IntersectionObserver;
|
|
1544
|
+
if (IO) {
|
|
1545
|
+
this.loader = new IO(
|
|
1546
|
+
(entries) => {
|
|
1547
|
+
if (entries.some((e) => e.isIntersecting)) ctx.extendList(ctx.view.range.endDay + Math.max(7, opts.listDayCount));
|
|
1548
|
+
},
|
|
1549
|
+
// Auto height scrolls the page, so watch the viewport; fixed height scrolls the list.
|
|
1550
|
+
{ root: opts.height === "auto" ? null : this.scroller, rootMargin: LOAD_AHEAD }
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
const RO = globals.ResizeObserver;
|
|
1554
|
+
if (RO) {
|
|
1555
|
+
const strip = this.strip.el;
|
|
1556
|
+
this.stripSize = new RO(() => this.el.style.setProperty("--tnc-strip-h", `${strip.offsetHeight}px`));
|
|
1557
|
+
this.stripSize.observe(strip);
|
|
1558
|
+
}
|
|
1559
|
+
win.addEventListener("scroll", this.onScroll, { capture: true, passive: true });
|
|
1560
|
+
this.scroller.addEventListener("wheel", this.fold, { passive: true });
|
|
1561
|
+
this.scroller.addEventListener("touchmove", this.fold, { passive: true });
|
|
1350
1562
|
}
|
|
1351
1563
|
ctx;
|
|
1352
1564
|
el;
|
|
@@ -1354,6 +1566,13 @@ var ListView = class {
|
|
|
1354
1566
|
body;
|
|
1355
1567
|
pools = /* @__PURE__ */ new Map();
|
|
1356
1568
|
sections = /* @__PURE__ */ new Map();
|
|
1569
|
+
strip = null;
|
|
1570
|
+
sentinel = null;
|
|
1571
|
+
loader = null;
|
|
1572
|
+
stripSize = null;
|
|
1573
|
+
frame = null;
|
|
1574
|
+
/** A picked day stays marked until the list is scrolled by hand. */
|
|
1575
|
+
pinned = false;
|
|
1357
1576
|
itemsOn(day) {
|
|
1358
1577
|
const { store, opts } = this.ctx;
|
|
1359
1578
|
const out = store.spanningIn(day, day + 1, true);
|
|
@@ -1364,11 +1583,12 @@ var ListView = class {
|
|
|
1364
1583
|
render(_change) {
|
|
1365
1584
|
const { ctx } = this;
|
|
1366
1585
|
const { doc, days } = ctx;
|
|
1586
|
+
const agenda = !!this.strip;
|
|
1367
1587
|
const keep = /* @__PURE__ */ new Set();
|
|
1368
1588
|
let prev = null;
|
|
1369
1589
|
for (const day of days) {
|
|
1370
1590
|
const items = this.itemsOn(day);
|
|
1371
|
-
if (!items.length) continue;
|
|
1591
|
+
if (!items.length && !agenda) continue;
|
|
1372
1592
|
keep.add(day);
|
|
1373
1593
|
let section = this.sections.get(day);
|
|
1374
1594
|
let pool = this.pools.get(day);
|
|
@@ -1385,6 +1605,7 @@ var ListView = class {
|
|
|
1385
1605
|
this.sections.set(day, section);
|
|
1386
1606
|
this.pools.set(day, pool);
|
|
1387
1607
|
}
|
|
1608
|
+
section.classList.toggle("is-empty", !items.length);
|
|
1388
1609
|
pool.begin();
|
|
1389
1610
|
for (const n of items) {
|
|
1390
1611
|
pool.place(n.id, {
|
|
@@ -1422,11 +1643,62 @@ var ListView = class {
|
|
|
1422
1643
|
this.body.append(empty);
|
|
1423
1644
|
}
|
|
1424
1645
|
} else empty?.remove();
|
|
1646
|
+
this.strip?.draw();
|
|
1647
|
+
if (this.loader && this.sentinel) {
|
|
1648
|
+
this.loader.unobserve(this.sentinel);
|
|
1649
|
+
this.loader.observe(this.sentinel);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
/** A strip day: the host hears of it, and the list brings it to the top. */
|
|
1653
|
+
pick(day, ev) {
|
|
1654
|
+
this.ctx.dateClick(day, ev);
|
|
1655
|
+
this.ctx.showListDay(day);
|
|
1656
|
+
}
|
|
1657
|
+
/** Brings a day's section to the top, under the strip. */
|
|
1658
|
+
scrollToDay(day) {
|
|
1659
|
+
const section = this.sections.get(day);
|
|
1660
|
+
if (!section) return;
|
|
1661
|
+
if (this.strip) this.el.style.setProperty("--tnc-strip-h", `${this.strip.el.offsetHeight}px`);
|
|
1662
|
+
section.scrollIntoView({ block: "start" });
|
|
1663
|
+
this.strip?.setFocus(day);
|
|
1664
|
+
this.pinned = true;
|
|
1665
|
+
}
|
|
1666
|
+
fold = () => {
|
|
1667
|
+
this.pinned = false;
|
|
1668
|
+
if (this.strip?.expanded) this.strip.setExpanded(false);
|
|
1669
|
+
};
|
|
1670
|
+
onScroll = () => {
|
|
1671
|
+
if (this.frame !== null) return;
|
|
1672
|
+
this.frame = this.ctx.win.requestAnimationFrame(() => {
|
|
1673
|
+
this.frame = null;
|
|
1674
|
+
this.followTop();
|
|
1675
|
+
});
|
|
1676
|
+
};
|
|
1677
|
+
/** Marks the first day not hidden under the strip. */
|
|
1678
|
+
followTop() {
|
|
1679
|
+
if (!this.strip || this.pinned) return;
|
|
1680
|
+
const edge = this.strip.el.getBoundingClientRect().bottom + 1;
|
|
1681
|
+
for (const day of this.ctx.days) {
|
|
1682
|
+
const section = this.sections.get(day);
|
|
1683
|
+
if (section && section.getBoundingClientRect().bottom > edge) {
|
|
1684
|
+
this.strip.setFocus(day);
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1425
1688
|
}
|
|
1426
1689
|
showSelection(_sel) {
|
|
1427
1690
|
}
|
|
1428
|
-
hit(
|
|
1429
|
-
|
|
1691
|
+
hit(x, y) {
|
|
1692
|
+
const el = this.ctx.doc.elementFromPoint(x, y);
|
|
1693
|
+
const stripDay = el && this.strip ? this.strip.dayAt(el) : null;
|
|
1694
|
+
this.strip?.markTarget(stripDay !== null && this.ctx.draggingId ? stripDay : null);
|
|
1695
|
+
if (stripDay !== null) return { kind: "day", day: stripDay };
|
|
1696
|
+
const section = el?.closest(".tnc-list-section");
|
|
1697
|
+
const day = section && this.body.contains(section) ? parseDay(section.dataset.date ?? "") : null;
|
|
1698
|
+
return day === null ? null : { kind: "day", day };
|
|
1699
|
+
}
|
|
1700
|
+
dragEnded() {
|
|
1701
|
+
this.strip?.markTarget(null);
|
|
1430
1702
|
}
|
|
1431
1703
|
getScroll() {
|
|
1432
1704
|
return { top: this.scroller.scrollTop, left: 0 };
|
|
@@ -1438,6 +1710,12 @@ var ListView = class {
|
|
|
1438
1710
|
return [...this.pools.values()].flatMap((p) => p.elementsFor(id));
|
|
1439
1711
|
}
|
|
1440
1712
|
destroy() {
|
|
1713
|
+
const { win } = this.ctx;
|
|
1714
|
+
win.removeEventListener("scroll", this.onScroll, { capture: true });
|
|
1715
|
+
if (this.frame !== null) win.cancelAnimationFrame(this.frame);
|
|
1716
|
+
this.loader?.disconnect();
|
|
1717
|
+
this.stripSize?.disconnect();
|
|
1718
|
+
this.strip?.destroy();
|
|
1441
1719
|
for (const p of this.pools.values()) p.clear();
|
|
1442
1720
|
this.el.remove();
|
|
1443
1721
|
}
|
|
@@ -2579,6 +2857,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2579
2857
|
|
|
2580
2858
|
// src/dom/calendar.ts
|
|
2581
2859
|
var EXTERNAL_ID = "__tnc_external__";
|
|
2860
|
+
var MAX_AGENDA_DAYS = 366;
|
|
2582
2861
|
function sameOption(a, b, depth = 0) {
|
|
2583
2862
|
if (a === b) return true;
|
|
2584
2863
|
if (typeof a === "function" && typeof b === "function") return true;
|
|
@@ -2651,6 +2930,8 @@ var Calendar = class {
|
|
|
2651
2930
|
viewHost;
|
|
2652
2931
|
current = null;
|
|
2653
2932
|
anchor = 0;
|
|
2933
|
+
/** End of an agenda list grown past its `listDayCount`. */
|
|
2934
|
+
listEnd = null;
|
|
2654
2935
|
session = null;
|
|
2655
2936
|
selection = null;
|
|
2656
2937
|
suppressClick = false;
|
|
@@ -2843,6 +3124,10 @@ var Calendar = class {
|
|
|
2843
3124
|
if (!p) return;
|
|
2844
3125
|
const day = p.kind === "day" ? p.day : dayOf(p.ms, this.zone);
|
|
2845
3126
|
const ms = p.kind === "day" ? toInstant(p.day, 0, this.zone) : p.ms;
|
|
3127
|
+
if (this.agendaList()) {
|
|
3128
|
+
this.showListDay(day);
|
|
3129
|
+
return;
|
|
3130
|
+
}
|
|
2846
3131
|
const { startDay, endDay } = this.view.range;
|
|
2847
3132
|
if (day < startDay || day >= endDay) {
|
|
2848
3133
|
this.anchor = day;
|
|
@@ -2926,7 +3211,8 @@ var Calendar = class {
|
|
|
2926
3211
|
return out;
|
|
2927
3212
|
}
|
|
2928
3213
|
isEditable(n, what) {
|
|
2929
|
-
if (n.id === EXTERNAL_ID || this.opts.view === "
|
|
3214
|
+
if (n.id === EXTERNAL_ID || this.opts.view === "year") return false;
|
|
3215
|
+
if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
|
|
2930
3216
|
const it = n.item;
|
|
2931
3217
|
const base = it.editable ?? this.opts.editable;
|
|
2932
3218
|
const specific = what === "start" ? it.startEditable : it.durationEditable;
|
|
@@ -3123,7 +3409,8 @@ var Calendar = class {
|
|
|
3123
3409
|
}
|
|
3124
3410
|
case "list": {
|
|
3125
3411
|
const s = this.opts.listStart === "week" ? startOfWeek(a, this.firstDay) : a;
|
|
3126
|
-
|
|
3412
|
+
const end = s + Math.max(1, listDayCount);
|
|
3413
|
+
return { startDay: s, endDay: this.opts.listAgenda && this.listEnd !== null ? Math.max(end, this.listEnd) : end };
|
|
3127
3414
|
}
|
|
3128
3415
|
case "month": {
|
|
3129
3416
|
const s = startOfWeek(startOfMonth(a), this.firstDay);
|
|
@@ -3161,6 +3448,27 @@ var Calendar = class {
|
|
|
3161
3448
|
const duration = Math.max(1, parseDurationMinutes(this.opts.slotDuration, 30));
|
|
3162
3449
|
const snap = Math.max(1, parseDurationMinutes(this.opts.snapDuration ?? null, duration));
|
|
3163
3450
|
this.slot = { min, max, duration, snap, scroll: parseDurationMinutes(this.opts.scrollTime, 480) };
|
|
3451
|
+
if (navigated) this.listEnd = null;
|
|
3452
|
+
const rangeChanged = this.applyRange();
|
|
3453
|
+
this.conflicts = dependencyConflicts(this.store);
|
|
3454
|
+
this.root.className = "tnc";
|
|
3455
|
+
this.root.classList.add(`tnc--${this.opts.view}`, `tnc--height-${this.opts.height}`);
|
|
3456
|
+
if (!this.opts.timeGrid) this.root.classList.add("tnc--no-time-grid");
|
|
3457
|
+
this.renderToolbar();
|
|
3458
|
+
this.current?.destroy();
|
|
3459
|
+
this.current = this.createView();
|
|
3460
|
+
this.viewHost.replaceChildren(this.current.el);
|
|
3461
|
+
this.current.render(null);
|
|
3462
|
+
if (keep && this.current instanceof TimelineView) {
|
|
3463
|
+
this.current.setScroll({ top: keep.top, left: 0 });
|
|
3464
|
+
this.current.placeInstant(keep.ms, keep.vx);
|
|
3465
|
+
} else if (scroll) this.current.setScroll(scroll);
|
|
3466
|
+
else if (this.current instanceof TimeGridView) this.current.scrollToTime(this.slot.scroll);
|
|
3467
|
+
else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
3468
|
+
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
3469
|
+
}
|
|
3470
|
+
/** Sets the visible days and view info from the anchor; true when the range moved. */
|
|
3471
|
+
applyRange() {
|
|
3164
3472
|
const { startDay, endDay } = this.computeRange();
|
|
3165
3473
|
const hidden = new Set(this.opts.hiddenDays);
|
|
3166
3474
|
if (!this.opts.weekends) {
|
|
@@ -3180,23 +3488,32 @@ var Calendar = class {
|
|
|
3180
3488
|
const prevView = this.view;
|
|
3181
3489
|
this.view = { type: this.opts.view, range, anchor: this.anchor, title: "" };
|
|
3182
3490
|
this.view.title = this.computeTitle(startDay, endDay);
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
this.
|
|
3191
|
-
|
|
3192
|
-
if (
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
if (
|
|
3491
|
+
return !prevView || prevView.type !== this.view.type || prevView.range.startDay !== startDay || prevView.range.endDay !== endDay;
|
|
3492
|
+
}
|
|
3493
|
+
agendaList() {
|
|
3494
|
+
return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
|
|
3495
|
+
}
|
|
3496
|
+
extendList(endDay) {
|
|
3497
|
+
if (!this.agendaList()) return;
|
|
3498
|
+
const { startDay, endDay: current } = this.view.range;
|
|
3499
|
+
const end = Math.min(endDay, startDay + MAX_AGENDA_DAYS);
|
|
3500
|
+
if (end <= current) return;
|
|
3501
|
+
this.listEnd = end;
|
|
3502
|
+
this.applyRange();
|
|
3503
|
+
this.current?.render(null);
|
|
3504
|
+
this.opts.onRangeChange?.(this.view);
|
|
3505
|
+
}
|
|
3506
|
+
showListDay(day) {
|
|
3507
|
+
if (!this.agendaList()) return;
|
|
3508
|
+
if (day < this.view.range.startDay || day - this.view.range.startDay >= MAX_AGENDA_DAYS) {
|
|
3509
|
+
this.anchor = day;
|
|
3510
|
+
this.rebuild(true);
|
|
3511
|
+
}
|
|
3512
|
+
this.extendList(day + 2 * Math.max(7, this.opts.listDayCount));
|
|
3513
|
+
this.agendaList()?.scrollToDay(day);
|
|
3514
|
+
}
|
|
3515
|
+
dateClick(day, jsEvent) {
|
|
3516
|
+
this.clickSlot({ kind: "day", day }, jsEvent, true);
|
|
3200
3517
|
}
|
|
3201
3518
|
createView() {
|
|
3202
3519
|
switch (this.opts.view) {
|
|
@@ -3499,8 +3816,9 @@ var Calendar = class {
|
|
|
3499
3816
|
};
|
|
3500
3817
|
this.session = s;
|
|
3501
3818
|
if (touch && itemEl?.classList.contains("is-draggable")) {
|
|
3502
|
-
const
|
|
3503
|
-
|
|
3819
|
+
const x = scrollerOf(itemEl, "x");
|
|
3820
|
+
const y = scrollerOf(itemEl, "y");
|
|
3821
|
+
s.pan = { x, y, left: x?.scrollLeft ?? 0, top: y?.scrollTop ?? 0, active: false };
|
|
3504
3822
|
}
|
|
3505
3823
|
if (touch) {
|
|
3506
3824
|
s.timer = this.win.setTimeout(() => {
|
|
@@ -3541,8 +3859,8 @@ var Calendar = class {
|
|
|
3541
3859
|
if (!pan.active && s.timer !== null) this.win.clearTimeout(s.timer);
|
|
3542
3860
|
s.timer = null;
|
|
3543
3861
|
pan.active = true;
|
|
3544
|
-
pan.
|
|
3545
|
-
pan.
|
|
3862
|
+
if (pan.x) pan.x.scrollLeft = pan.left - (ev.clientX - s.x0);
|
|
3863
|
+
if (pan.y) pan.y.scrollTop = pan.top - (ev.clientY - s.y0);
|
|
3546
3864
|
} else if (dist > 8) this.endSession(null, true);
|
|
3547
3865
|
return;
|
|
3548
3866
|
}
|
|
@@ -3841,6 +4159,7 @@ var Calendar = class {
|
|
|
3841
4159
|
this.draggingId = null;
|
|
3842
4160
|
this.dragRow = null;
|
|
3843
4161
|
if (draggedId) for (const el of this.elementsFor(draggedId)) el.classList.remove("is-dragging");
|
|
4162
|
+
this.current?.dragEnded?.();
|
|
3844
4163
|
if (!s.active) return;
|
|
3845
4164
|
if (s.mode === "select") {
|
|
3846
4165
|
if (cancel) this.unselectSilently();
|
|
@@ -4127,6 +4446,16 @@ var Calendar = class {
|
|
|
4127
4446
|
});
|
|
4128
4447
|
};
|
|
4129
4448
|
};
|
|
4449
|
+
function scrollerOf(el, axis) {
|
|
4450
|
+
const doc = el.ownerDocument;
|
|
4451
|
+
const win = doc.defaultView;
|
|
4452
|
+
for (let p = el.parentElement; p && p !== doc.body && p !== doc.documentElement; p = p.parentElement) {
|
|
4453
|
+
const overflow = win?.getComputedStyle(p)[axis === "x" ? "overflowX" : "overflowY"] ?? "";
|
|
4454
|
+
const room = axis === "x" ? p.scrollWidth > p.clientWidth : p.scrollHeight > p.clientHeight;
|
|
4455
|
+
if (room && /auto|scroll|overlay/.test(overflow)) return p;
|
|
4456
|
+
}
|
|
4457
|
+
return doc.scrollingElement;
|
|
4458
|
+
}
|
|
4130
4459
|
|
|
4131
4460
|
// src/tasknotes/bases.ts
|
|
4132
4461
|
var FROM_BASES = {
|