@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.
@@ -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
- this.el = h(ctx.doc, "div", "tnc-list");
1346
- this.scroller = h(ctx.doc, "div", "tnc-list-scroll");
1347
- this.body = h(ctx.doc, "div", "tnc-list-body", { role: "list", "aria-label": ctx.view.title });
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(_x, _y) {
1429
- return null;
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 === "list" || this.opts.view === "year") return false;
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
- return { startDay: s, endDay: s + Math.max(1, listDayCount) };
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
- this.conflicts = dependencyConflicts(this.store);
3184
- this.root.className = "tnc";
3185
- this.root.classList.add(`tnc--${this.opts.view}`, `tnc--height-${this.opts.height}`);
3186
- if (!this.opts.timeGrid) this.root.classList.add("tnc--no-time-grid");
3187
- this.renderToolbar();
3188
- this.current?.destroy();
3189
- this.current = this.createView();
3190
- this.viewHost.replaceChildren(this.current.el);
3191
- this.current.render(null);
3192
- if (keep && this.current instanceof TimelineView) {
3193
- this.current.setScroll({ top: keep.top, left: 0 });
3194
- this.current.placeInstant(keep.ms, keep.vx);
3195
- } else if (scroll) this.current.setScroll(scroll);
3196
- else if (this.current instanceof TimeGridView) this.current.scrollToTime(this.slot.scroll);
3197
- else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
3198
- const rangeChanged = !prevView || prevView.type !== this.view.type || prevView.range.startDay !== startDay || prevView.range.endDay !== endDay;
3199
- if (rangeChanged) this.opts.onRangeChange?.(this.view);
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 el = itemEl.closest(".tnc-tg-scroll, .tnc-dg-scroll, .tnc-tl-scroll, .tnc-list-scroll, .tnc-year-scroll");
3503
- if (el) s.pan = { el, left: el.scrollLeft, top: el.scrollTop, active: false };
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.el.scrollLeft = pan.left - (ev.clientX - s.x0);
3545
- pan.el.scrollTop = pan.top - (ev.clientY - s.y0);
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 = {