@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.
@@ -708,6 +708,7 @@ function defaultOptions() {
708
708
  dayCount: 3,
709
709
  listDayCount: 7,
710
710
  listStart: "date",
711
+ listAgenda: false,
711
712
  monthDisplay: "full",
712
713
  timeZone: "local",
713
714
  locale: void 0,
@@ -757,6 +758,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
757
758
  "dayCount",
758
759
  "listDayCount",
759
760
  "listStart",
761
+ "listAgenda",
760
762
  "monthDisplay",
761
763
  "timeZone",
762
764
  "locale",
@@ -1246,15 +1248,225 @@ var DayGridView = class {
1246
1248
  }
1247
1249
  };
1248
1250
 
1251
+ // src/dom/views/strip.ts
1252
+ var SWIPE_PX = 40;
1253
+ var DateStrip = class {
1254
+ constructor(ctx, pick, focus) {
1255
+ this.ctx = ctx;
1256
+ this.pick = pick;
1257
+ const { doc, text } = ctx;
1258
+ this.focus = focus;
1259
+ this.shown = startOfWeek(focus, ctx.firstDay);
1260
+ this.el = h(doc, "div", "tnc-strip");
1261
+ const head = h(doc, "div", "tnc-strip-head");
1262
+ this.title = h(doc, "button", "tnc-strip-title", { type: "button", "aria-expanded": "false" });
1263
+ this.title.addEventListener("click", () => this.setExpanded(!this.expanded));
1264
+ const prev = h(doc, "button", "tnc-strip-nav tnc-strip-prev", { type: "button", "aria-label": text.prev, title: text.prev }, "\u2039");
1265
+ const next = h(doc, "button", "tnc-strip-nav tnc-strip-next", { type: "button", "aria-label": text.next, title: text.next }, "\u203A");
1266
+ prev.addEventListener("click", () => this.step(-1));
1267
+ next.addEventListener("click", () => this.step(1));
1268
+ this.slot = h(doc, "div", "tnc-strip-slot", { "data-tnc-no-drag": "" });
1269
+ head.append(this.title, prev, next, this.slot);
1270
+ this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
1271
+ this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
1272
+ const handle = h(doc, "button", "tnc-strip-handle", { type: "button", tabindex: "-1", "aria-hidden": "true" });
1273
+ handle.addEventListener("click", () => this.setExpanded(!this.expanded));
1274
+ this.el.append(head, this.dows, this.grid, handle);
1275
+ this.grid.addEventListener("click", this.onClick);
1276
+ this.grid.addEventListener("keydown", this.onKeyDown);
1277
+ this.el.addEventListener("pointerdown", this.onPointerDown);
1278
+ this.el.addEventListener("pointerup", this.onPointerUp);
1279
+ this.el.addEventListener("pointercancel", this.onPointerCancel);
1280
+ this.el.addEventListener("click", this.onClickCapture, true);
1281
+ this.draw();
1282
+ ctx.opts.stripSlotDidMount?.({ el: this.slot });
1283
+ }
1284
+ ctx;
1285
+ pick;
1286
+ el;
1287
+ title;
1288
+ dows;
1289
+ grid;
1290
+ slot;
1291
+ /** Day at the top of the list. */
1292
+ focus;
1293
+ /** First day of the week shown, or the month shown when expanded. */
1294
+ shown;
1295
+ target = null;
1296
+ swipe = null;
1297
+ swallowClick = false;
1298
+ expanded = false;
1299
+ /** The list scrolled: mark its top day and keep it in view. */
1300
+ setFocus(day) {
1301
+ if (day === this.focus) return;
1302
+ this.focus = day;
1303
+ if (!this.expanded) this.shown = startOfWeek(day, this.ctx.firstDay);
1304
+ else if (startOfMonth(day) !== this.shown) this.shown = startOfMonth(day);
1305
+ this.draw();
1306
+ }
1307
+ setExpanded(on) {
1308
+ if (on === this.expanded) return;
1309
+ this.expanded = on;
1310
+ this.shown = on ? startOfMonth(this.focus) : startOfWeek(this.focus, this.ctx.firstDay);
1311
+ this.draw();
1312
+ }
1313
+ /** Highlights the day a dragged item would land on. */
1314
+ markTarget(day) {
1315
+ if (day === this.target) return;
1316
+ this.target = day;
1317
+ for (const cell of this.grid.querySelectorAll(".tnc-strip-day"))
1318
+ cell.classList.toggle("is-drop-target", day !== null && cell.dataset.date === this.ctx.dayArg(day).dateStr);
1319
+ }
1320
+ dayAt(el) {
1321
+ const cell = el.closest(".tnc-strip-day");
1322
+ return cell && this.grid.contains(cell) ? parseDay(cell.dataset.date ?? "") : null;
1323
+ }
1324
+ step(dir) {
1325
+ this.shown = this.expanded ? addMonths(this.shown, dir) : this.shown + 7 * dir;
1326
+ this.draw();
1327
+ }
1328
+ days() {
1329
+ if (!this.expanded) return Array.from({ length: 7 }, (_, i) => this.shown + i);
1330
+ const first = startOfWeek(this.shown, this.ctx.firstDay);
1331
+ const end = addMonths(this.shown, 1);
1332
+ const weeks = Math.ceil((end - first) / 7);
1333
+ return Array.from({ length: weeks * 7 }, (_, i) => first + i);
1334
+ }
1335
+ /** Redraws the cells; also called on every list render to refresh the dots. */
1336
+ draw() {
1337
+ const { ctx } = this;
1338
+ const { doc, fmt, store } = ctx;
1339
+ const days = this.days();
1340
+ const titleDay = this.expanded ? this.shown : days.includes(this.focus) ? this.focus : this.shown + 3;
1341
+ this.title.replaceChildren(fmt.monthYear(titleDay), h(doc, "span", "tnc-strip-caret", { "aria-hidden": "true" }));
1342
+ this.title.setAttribute("aria-expanded", String(this.expanded));
1343
+ this.el.classList.toggle("is-expanded", this.expanded);
1344
+ this.dows.replaceChildren(...days.slice(0, 7).map((d) => h(doc, "span", null, null, fmt.weekdayNarrow(d))));
1345
+ const focused = this.grid.contains(doc.activeElement) ? doc.activeElement.dataset.date : void 0;
1346
+ const month = ymdOfDay(this.shown).month;
1347
+ const tabDay = days.includes(this.focus) ? this.focus : days[0];
1348
+ const cells = days.map((d) => {
1349
+ const arg = ctx.dayArg(d);
1350
+ const count = store.inRange(d, d + 1).filter((n) => n.item.display !== "background" && n.item.display !== "none").length;
1351
+ const first = ymdOfDay(d).day === 1 && d !== days[0] && !this.expanded;
1352
+ const label = fmt.longDate(d);
1353
+ const cell = h(
1354
+ doc,
1355
+ "button",
1356
+ [
1357
+ "tnc-strip-day",
1358
+ ...ctx.dayClasses(d),
1359
+ d === this.focus ? "is-focus" : "",
1360
+ count ? "has-items" : "",
1361
+ this.expanded && ymdOfDay(d).month !== month ? "is-other-month" : "",
1362
+ d === this.target ? "is-drop-target" : ""
1363
+ ].filter(Boolean).join(" "),
1364
+ {
1365
+ type: "button",
1366
+ "data-date": arg.dateStr,
1367
+ "aria-label": count ? `${label}, ${ctx.text.itemCount(count)}` : label,
1368
+ "aria-pressed": String(d === this.focus),
1369
+ tabindex: d === tabDay ? "0" : "-1"
1370
+ }
1371
+ );
1372
+ if (first) cell.append(h(doc, "span", "tnc-strip-month", { "aria-hidden": "true" }, fmt.monthShort(d)));
1373
+ cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d)), h(doc, "span", "tnc-strip-dot", { "aria-hidden": "true" }));
1374
+ return cell;
1375
+ });
1376
+ this.grid.replaceChildren(...cells);
1377
+ this.grid.setAttribute("aria-label", fmt.monthYear(titleDay));
1378
+ if (focused) this.grid.querySelector(`[data-date="${focused}"]`)?.focus({ preventScroll: true });
1379
+ }
1380
+ onClick = (ev) => {
1381
+ const day = this.dayAt(ev.target);
1382
+ if (day === null) return;
1383
+ if (this.expanded) this.setExpanded(false);
1384
+ this.pick(day, ev);
1385
+ };
1386
+ onKeyDown = (ev) => {
1387
+ const day = this.dayAt(ev.target);
1388
+ if (day === null) return;
1389
+ const move = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[ev.key];
1390
+ if (move === void 0) return;
1391
+ ev.preventDefault();
1392
+ const next = day + move;
1393
+ const days = this.days();
1394
+ if (next < days[0] || next > days[days.length - 1]) {
1395
+ this.shown = this.expanded ? startOfMonth(next) : startOfWeek(next, this.ctx.firstDay);
1396
+ this.draw();
1397
+ }
1398
+ const cell = this.grid.querySelector(`[data-date="${this.ctx.dayArg(next).dateStr}"]`);
1399
+ if (!cell) return;
1400
+ for (const c of this.grid.querySelectorAll("[tabindex='0']")) c.setAttribute("tabindex", "-1");
1401
+ cell.setAttribute("tabindex", "0");
1402
+ cell.focus();
1403
+ };
1404
+ onPointerDown = (ev) => {
1405
+ if (ev.button !== 0 || this.ctx.draggingId) return;
1406
+ this.swipe = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
1407
+ };
1408
+ onPointerUp = (ev) => {
1409
+ const s = this.swipe;
1410
+ this.swipe = null;
1411
+ if (!s || s.id !== ev.pointerId) return;
1412
+ const dx = ev.clientX - s.x;
1413
+ if (Math.abs(dx) < SWIPE_PX || Math.abs(dx) < Math.abs(ev.clientY - s.y)) return;
1414
+ this.swallowClick = true;
1415
+ this.step(dx < 0 ? 1 : -1);
1416
+ };
1417
+ onPointerCancel = () => {
1418
+ this.swipe = null;
1419
+ };
1420
+ /** A swipe that ends on a day is not a pick. */
1421
+ onClickCapture = (ev) => {
1422
+ if (!this.swallowClick) return;
1423
+ this.swallowClick = false;
1424
+ ev.stopPropagation();
1425
+ ev.preventDefault();
1426
+ };
1427
+ destroy() {
1428
+ this.ctx.opts.stripSlotWillUnmount?.({ el: this.slot });
1429
+ this.el.remove();
1430
+ }
1431
+ };
1432
+
1249
1433
  // src/dom/views/list.ts
1434
+ var LOAD_AHEAD = "0px 0px 600px 0px";
1250
1435
  var ListView = class {
1251
1436
  constructor(ctx) {
1252
1437
  this.ctx = ctx;
1253
- this.el = h(ctx.doc, "div", "tnc-list");
1254
- this.scroller = h(ctx.doc, "div", "tnc-list-scroll");
1255
- this.body = h(ctx.doc, "div", "tnc-list-body", { role: "list", "aria-label": ctx.view.title });
1438
+ const { doc, win, opts } = ctx;
1439
+ this.el = h(doc, "div", "tnc-list");
1440
+ this.scroller = h(doc, "div", "tnc-list-scroll");
1441
+ this.body = h(doc, "div", "tnc-list-body", { role: "list", "aria-label": ctx.view.title });
1256
1442
  this.scroller.append(this.body);
1257
1443
  this.el.append(this.scroller);
1444
+ if (!opts.listAgenda) return;
1445
+ this.el.classList.add("tnc-list--agenda");
1446
+ this.strip = new DateStrip(ctx, (day, ev) => this.pick(day, ev), ctx.view.range.startDay);
1447
+ this.el.prepend(this.strip.el);
1448
+ this.sentinel = h(doc, "div", "tnc-list-more", { "aria-hidden": "true" });
1449
+ this.scroller.append(this.sentinel);
1450
+ const globals = win;
1451
+ const IO = globals.IntersectionObserver;
1452
+ if (IO) {
1453
+ this.loader = new IO(
1454
+ (entries) => {
1455
+ if (entries.some((e) => e.isIntersecting)) ctx.extendList(ctx.view.range.endDay + Math.max(7, opts.listDayCount));
1456
+ },
1457
+ // Auto height scrolls the page, so watch the viewport; fixed height scrolls the list.
1458
+ { root: opts.height === "auto" ? null : this.scroller, rootMargin: LOAD_AHEAD }
1459
+ );
1460
+ }
1461
+ const RO = globals.ResizeObserver;
1462
+ if (RO) {
1463
+ const strip = this.strip.el;
1464
+ this.stripSize = new RO(() => this.el.style.setProperty("--tnc-strip-h", `${strip.offsetHeight}px`));
1465
+ this.stripSize.observe(strip);
1466
+ }
1467
+ win.addEventListener("scroll", this.onScroll, { capture: true, passive: true });
1468
+ this.scroller.addEventListener("wheel", this.fold, { passive: true });
1469
+ this.scroller.addEventListener("touchmove", this.fold, { passive: true });
1258
1470
  }
1259
1471
  ctx;
1260
1472
  el;
@@ -1262,6 +1474,13 @@ var ListView = class {
1262
1474
  body;
1263
1475
  pools = /* @__PURE__ */ new Map();
1264
1476
  sections = /* @__PURE__ */ new Map();
1477
+ strip = null;
1478
+ sentinel = null;
1479
+ loader = null;
1480
+ stripSize = null;
1481
+ frame = null;
1482
+ /** A picked day stays marked until the list is scrolled by hand. */
1483
+ pinned = false;
1265
1484
  itemsOn(day) {
1266
1485
  const { store, opts } = this.ctx;
1267
1486
  const out = store.spanningIn(day, day + 1, true);
@@ -1272,11 +1491,12 @@ var ListView = class {
1272
1491
  render(_change) {
1273
1492
  const { ctx } = this;
1274
1493
  const { doc, days } = ctx;
1494
+ const agenda = !!this.strip;
1275
1495
  const keep = /* @__PURE__ */ new Set();
1276
1496
  let prev = null;
1277
1497
  for (const day of days) {
1278
1498
  const items = this.itemsOn(day);
1279
- if (!items.length) continue;
1499
+ if (!items.length && !agenda) continue;
1280
1500
  keep.add(day);
1281
1501
  let section = this.sections.get(day);
1282
1502
  let pool = this.pools.get(day);
@@ -1293,6 +1513,7 @@ var ListView = class {
1293
1513
  this.sections.set(day, section);
1294
1514
  this.pools.set(day, pool);
1295
1515
  }
1516
+ section.classList.toggle("is-empty", !items.length);
1296
1517
  pool.begin();
1297
1518
  for (const n of items) {
1298
1519
  pool.place(n.id, {
@@ -1330,11 +1551,62 @@ var ListView = class {
1330
1551
  this.body.append(empty);
1331
1552
  }
1332
1553
  } else empty?.remove();
1554
+ this.strip?.draw();
1555
+ if (this.loader && this.sentinel) {
1556
+ this.loader.unobserve(this.sentinel);
1557
+ this.loader.observe(this.sentinel);
1558
+ }
1559
+ }
1560
+ /** A strip day: the host hears of it, and the list brings it to the top. */
1561
+ pick(day, ev) {
1562
+ this.ctx.dateClick(day, ev);
1563
+ this.ctx.showListDay(day);
1564
+ }
1565
+ /** Brings a day's section to the top, under the strip. */
1566
+ scrollToDay(day) {
1567
+ const section = this.sections.get(day);
1568
+ if (!section) return;
1569
+ if (this.strip) this.el.style.setProperty("--tnc-strip-h", `${this.strip.el.offsetHeight}px`);
1570
+ section.scrollIntoView({ block: "start" });
1571
+ this.strip?.setFocus(day);
1572
+ this.pinned = true;
1573
+ }
1574
+ fold = () => {
1575
+ this.pinned = false;
1576
+ if (this.strip?.expanded) this.strip.setExpanded(false);
1577
+ };
1578
+ onScroll = () => {
1579
+ if (this.frame !== null) return;
1580
+ this.frame = this.ctx.win.requestAnimationFrame(() => {
1581
+ this.frame = null;
1582
+ this.followTop();
1583
+ });
1584
+ };
1585
+ /** Marks the first day not hidden under the strip. */
1586
+ followTop() {
1587
+ if (!this.strip || this.pinned) return;
1588
+ const edge = this.strip.el.getBoundingClientRect().bottom + 1;
1589
+ for (const day of this.ctx.days) {
1590
+ const section = this.sections.get(day);
1591
+ if (section && section.getBoundingClientRect().bottom > edge) {
1592
+ this.strip.setFocus(day);
1593
+ return;
1594
+ }
1595
+ }
1333
1596
  }
1334
1597
  showSelection(_sel) {
1335
1598
  }
1336
- hit(_x, _y) {
1337
- return null;
1599
+ hit(x, y) {
1600
+ const el = this.ctx.doc.elementFromPoint(x, y);
1601
+ const stripDay = el && this.strip ? this.strip.dayAt(el) : null;
1602
+ this.strip?.markTarget(stripDay !== null && this.ctx.draggingId ? stripDay : null);
1603
+ if (stripDay !== null) return { kind: "day", day: stripDay };
1604
+ const section = el?.closest(".tnc-list-section");
1605
+ const day = section && this.body.contains(section) ? parseDay(section.dataset.date ?? "") : null;
1606
+ return day === null ? null : { kind: "day", day };
1607
+ }
1608
+ dragEnded() {
1609
+ this.strip?.markTarget(null);
1338
1610
  }
1339
1611
  getScroll() {
1340
1612
  return { top: this.scroller.scrollTop, left: 0 };
@@ -1346,6 +1618,12 @@ var ListView = class {
1346
1618
  return [...this.pools.values()].flatMap((p) => p.elementsFor(id));
1347
1619
  }
1348
1620
  destroy() {
1621
+ const { win } = this.ctx;
1622
+ win.removeEventListener("scroll", this.onScroll, { capture: true });
1623
+ if (this.frame !== null) win.cancelAnimationFrame(this.frame);
1624
+ this.loader?.disconnect();
1625
+ this.stripSize?.disconnect();
1626
+ this.strip?.destroy();
1349
1627
  for (const p of this.pools.values()) p.clear();
1350
1628
  this.el.remove();
1351
1629
  }
@@ -2487,6 +2765,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
2487
2765
 
2488
2766
  // src/dom/calendar.ts
2489
2767
  var EXTERNAL_ID = "__tnc_external__";
2768
+ var MAX_AGENDA_DAYS = 366;
2490
2769
  function sameOption(a, b, depth = 0) {
2491
2770
  if (a === b) return true;
2492
2771
  if (typeof a === "function" && typeof b === "function") return true;
@@ -2559,6 +2838,8 @@ var Calendar = class {
2559
2838
  viewHost;
2560
2839
  current = null;
2561
2840
  anchor = 0;
2841
+ /** End of an agenda list grown past its `listDayCount`. */
2842
+ listEnd = null;
2562
2843
  session = null;
2563
2844
  selection = null;
2564
2845
  suppressClick = false;
@@ -2751,6 +3032,10 @@ var Calendar = class {
2751
3032
  if (!p) return;
2752
3033
  const day = p.kind === "day" ? p.day : dayOf(p.ms, this.zone);
2753
3034
  const ms = p.kind === "day" ? toInstant(p.day, 0, this.zone) : p.ms;
3035
+ if (this.agendaList()) {
3036
+ this.showListDay(day);
3037
+ return;
3038
+ }
2754
3039
  const { startDay, endDay } = this.view.range;
2755
3040
  if (day < startDay || day >= endDay) {
2756
3041
  this.anchor = day;
@@ -2834,7 +3119,8 @@ var Calendar = class {
2834
3119
  return out;
2835
3120
  }
2836
3121
  isEditable(n, what) {
2837
- if (n.id === EXTERNAL_ID || this.opts.view === "list" || this.opts.view === "year") return false;
3122
+ if (n.id === EXTERNAL_ID || this.opts.view === "year") return false;
3123
+ if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
2838
3124
  const it = n.item;
2839
3125
  const base = it.editable ?? this.opts.editable;
2840
3126
  const specific = what === "start" ? it.startEditable : it.durationEditable;
@@ -3031,7 +3317,8 @@ var Calendar = class {
3031
3317
  }
3032
3318
  case "list": {
3033
3319
  const s = this.opts.listStart === "week" ? startOfWeek(a, this.firstDay) : a;
3034
- return { startDay: s, endDay: s + Math.max(1, listDayCount) };
3320
+ const end = s + Math.max(1, listDayCount);
3321
+ return { startDay: s, endDay: this.opts.listAgenda && this.listEnd !== null ? Math.max(end, this.listEnd) : end };
3035
3322
  }
3036
3323
  case "month": {
3037
3324
  const s = startOfWeek(startOfMonth(a), this.firstDay);
@@ -3069,6 +3356,27 @@ var Calendar = class {
3069
3356
  const duration = Math.max(1, parseDurationMinutes(this.opts.slotDuration, 30));
3070
3357
  const snap = Math.max(1, parseDurationMinutes(this.opts.snapDuration ?? null, duration));
3071
3358
  this.slot = { min, max, duration, snap, scroll: parseDurationMinutes(this.opts.scrollTime, 480) };
3359
+ if (navigated) this.listEnd = null;
3360
+ const rangeChanged = this.applyRange();
3361
+ this.conflicts = dependencyConflicts(this.store);
3362
+ this.root.className = "tnc";
3363
+ this.root.classList.add(`tnc--${this.opts.view}`, `tnc--height-${this.opts.height}`);
3364
+ if (!this.opts.timeGrid) this.root.classList.add("tnc--no-time-grid");
3365
+ this.renderToolbar();
3366
+ this.current?.destroy();
3367
+ this.current = this.createView();
3368
+ this.viewHost.replaceChildren(this.current.el);
3369
+ this.current.render(null);
3370
+ if (keep && this.current instanceof TimelineView) {
3371
+ this.current.setScroll({ top: keep.top, left: 0 });
3372
+ this.current.placeInstant(keep.ms, keep.vx);
3373
+ } else if (scroll) this.current.setScroll(scroll);
3374
+ else if (this.current instanceof TimeGridView) this.current.scrollToTime(this.slot.scroll);
3375
+ else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
3376
+ if (rangeChanged) this.opts.onRangeChange?.(this.view);
3377
+ }
3378
+ /** Sets the visible days and view info from the anchor; true when the range moved. */
3379
+ applyRange() {
3072
3380
  const { startDay, endDay } = this.computeRange();
3073
3381
  const hidden = new Set(this.opts.hiddenDays);
3074
3382
  if (!this.opts.weekends) {
@@ -3088,23 +3396,32 @@ var Calendar = class {
3088
3396
  const prevView = this.view;
3089
3397
  this.view = { type: this.opts.view, range, anchor: this.anchor, title: "" };
3090
3398
  this.view.title = this.computeTitle(startDay, endDay);
3091
- this.conflicts = dependencyConflicts(this.store);
3092
- this.root.className = "tnc";
3093
- this.root.classList.add(`tnc--${this.opts.view}`, `tnc--height-${this.opts.height}`);
3094
- if (!this.opts.timeGrid) this.root.classList.add("tnc--no-time-grid");
3095
- this.renderToolbar();
3096
- this.current?.destroy();
3097
- this.current = this.createView();
3098
- this.viewHost.replaceChildren(this.current.el);
3099
- this.current.render(null);
3100
- if (keep && this.current instanceof TimelineView) {
3101
- this.current.setScroll({ top: keep.top, left: 0 });
3102
- this.current.placeInstant(keep.ms, keep.vx);
3103
- } else if (scroll) this.current.setScroll(scroll);
3104
- else if (this.current instanceof TimeGridView) this.current.scrollToTime(this.slot.scroll);
3105
- else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
3106
- const rangeChanged = !prevView || prevView.type !== this.view.type || prevView.range.startDay !== startDay || prevView.range.endDay !== endDay;
3107
- if (rangeChanged) this.opts.onRangeChange?.(this.view);
3399
+ return !prevView || prevView.type !== this.view.type || prevView.range.startDay !== startDay || prevView.range.endDay !== endDay;
3400
+ }
3401
+ agendaList() {
3402
+ return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
3403
+ }
3404
+ extendList(endDay) {
3405
+ if (!this.agendaList()) return;
3406
+ const { startDay, endDay: current } = this.view.range;
3407
+ const end = Math.min(endDay, startDay + MAX_AGENDA_DAYS);
3408
+ if (end <= current) return;
3409
+ this.listEnd = end;
3410
+ this.applyRange();
3411
+ this.current?.render(null);
3412
+ this.opts.onRangeChange?.(this.view);
3413
+ }
3414
+ showListDay(day) {
3415
+ if (!this.agendaList()) return;
3416
+ if (day < this.view.range.startDay || day - this.view.range.startDay >= MAX_AGENDA_DAYS) {
3417
+ this.anchor = day;
3418
+ this.rebuild(true);
3419
+ }
3420
+ this.extendList(day + 2 * Math.max(7, this.opts.listDayCount));
3421
+ this.agendaList()?.scrollToDay(day);
3422
+ }
3423
+ dateClick(day, jsEvent) {
3424
+ this.clickSlot({ kind: "day", day }, jsEvent, true);
3108
3425
  }
3109
3426
  createView() {
3110
3427
  switch (this.opts.view) {
@@ -3407,8 +3724,9 @@ var Calendar = class {
3407
3724
  };
3408
3725
  this.session = s;
3409
3726
  if (touch && itemEl?.classList.contains("is-draggable")) {
3410
- const el = itemEl.closest(".tnc-tg-scroll, .tnc-dg-scroll, .tnc-tl-scroll, .tnc-list-scroll, .tnc-year-scroll");
3411
- if (el) s.pan = { el, left: el.scrollLeft, top: el.scrollTop, active: false };
3727
+ const x = scrollerOf(itemEl, "x");
3728
+ const y = scrollerOf(itemEl, "y");
3729
+ s.pan = { x, y, left: x?.scrollLeft ?? 0, top: y?.scrollTop ?? 0, active: false };
3412
3730
  }
3413
3731
  if (touch) {
3414
3732
  s.timer = this.win.setTimeout(() => {
@@ -3449,8 +3767,8 @@ var Calendar = class {
3449
3767
  if (!pan.active && s.timer !== null) this.win.clearTimeout(s.timer);
3450
3768
  s.timer = null;
3451
3769
  pan.active = true;
3452
- pan.el.scrollLeft = pan.left - (ev.clientX - s.x0);
3453
- pan.el.scrollTop = pan.top - (ev.clientY - s.y0);
3770
+ if (pan.x) pan.x.scrollLeft = pan.left - (ev.clientX - s.x0);
3771
+ if (pan.y) pan.y.scrollTop = pan.top - (ev.clientY - s.y0);
3454
3772
  } else if (dist > 8) this.endSession(null, true);
3455
3773
  return;
3456
3774
  }
@@ -3749,6 +4067,7 @@ var Calendar = class {
3749
4067
  this.draggingId = null;
3750
4068
  this.dragRow = null;
3751
4069
  if (draggedId) for (const el of this.elementsFor(draggedId)) el.classList.remove("is-dragging");
4070
+ this.current?.dragEnded?.();
3752
4071
  if (!s.active) return;
3753
4072
  if (s.mode === "select") {
3754
4073
  if (cancel) this.unselectSilently();
@@ -4035,6 +4354,16 @@ var Calendar = class {
4035
4354
  });
4036
4355
  };
4037
4356
  };
4357
+ function scrollerOf(el, axis) {
4358
+ const doc = el.ownerDocument;
4359
+ const win = doc.defaultView;
4360
+ for (let p = el.parentElement; p && p !== doc.body && p !== doc.documentElement; p = p.parentElement) {
4361
+ const overflow = win?.getComputedStyle(p)[axis === "x" ? "overflowX" : "overflowY"] ?? "";
4362
+ const room = axis === "x" ? p.scrollWidth > p.clientWidth : p.scrollHeight > p.clientHeight;
4363
+ if (room && /auto|scroll|overlay/.test(overflow)) return p;
4364
+ }
4365
+ return doc.scrollingElement;
4366
+ }
4038
4367
 
4039
4368
  // src/react/index.tsx
4040
4369
  var import_jsx_runtime = require("react/jsx-runtime");