@tasknotes/calendar 0.1.7 → 0.1.8

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.
@@ -359,8 +359,9 @@ var DayGridView = class {
359
359
  constructor(ctx) {
360
360
  this.ctx = ctx;
361
361
  const { doc, opts, days } = ctx;
362
- this.dotsMode = opts.monthDisplay === "dots";
362
+ this.dotsMode = opts.monthDisplay === "dots" || opts.monthDisplay === "chips";
363
363
  this.el = h(doc, "div", this.dotsMode ? "tnc-dg tnc-dg--dots" : "tnc-dg");
364
+ this.el.classList.toggle("tnc-dg--chips", opts.monthDisplay === "chips");
364
365
  this.scroller = h(doc, "div", "tnc-dg-scroll");
365
366
  this.grid = h(doc, "div", "tnc-dg-grid", { role: "grid", "aria-label": ctx.view.title });
366
367
  const perWeek = this.daysPerWeek();
@@ -390,14 +391,14 @@ var DayGridView = class {
390
391
  const cell = h(doc, "div", cls.join(" "), { role: "gridcell", "aria-label": ctx.fmt.longDate(d), "data-date": arg.dateStr });
391
392
  if (this.dotsMode) cell.tabIndex = 0;
392
393
  const num = ctx.fmt.dayNumber(d);
393
- const label = ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num}` : num;
394
+ const label = !this.dotsMode && ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num}` : num;
394
395
  cell.append(
395
396
  h(
396
397
  doc,
397
398
  "div",
398
399
  "tnc-dg-top",
399
400
  null,
400
- opts.navLinks ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
401
+ opts.navLinks && !this.dotsMode ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
401
402
  )
402
403
  );
403
404
  dates.append(cell);
@@ -411,20 +412,20 @@ var DayGridView = class {
411
412
  layer.append(content);
412
413
  row.append(dates, layer);
413
414
  this.grid.append(row);
414
- const dots = this.dotsMode ? cells.map((cell) => {
415
+ const dots = cells.map((cell) => {
415
416
  const box = h(doc, "div", "tnc-dg-dots", { "aria-hidden": "true" });
416
417
  if (opts.dayMaxEvents === false) box.classList.add("is-unlimited");
417
418
  const count = h(doc, "span", "tnc-dg-dots-more");
418
419
  cell.append(box);
419
420
  return { box, pool: new ItemPool(ctx, box), count };
420
- }) : null;
421
+ });
421
422
  this.weeks.push({ days: wdays, row, cells, layer, sel, more, pool: new ItemPool(ctx, content), dots });
422
423
  }
423
424
  this.scroller.append(this.grid);
424
425
  this.el.append(this.scroller);
425
426
  this.el.classList.toggle("tnc-dg--auto", opts.height === "auto");
426
427
  const RO = ctx.win.ResizeObserver;
427
- if (RO && (opts.dayMaxEvents === true || this.dotsMode && opts.dayMaxEvents !== false)) {
428
+ if (RO) {
428
429
  this.resizeObs = new RO(() => {
429
430
  if (this.sizeKey() !== this.lastHeights) this.render(null);
430
431
  });
@@ -455,14 +456,35 @@ var DayGridView = class {
455
456
  }
456
457
  render(_change) {
457
458
  const { ctx } = this;
459
+ this.dotsMode = ctx.opts.monthDisplay !== "full" || this.grid.clientWidth / this.daysPerWeek() < 72;
460
+ this.el.classList.toggle("tnc-dg--dots", this.dotsMode);
461
+ this.el.classList.toggle("tnc-dg--compact", this.dotsMode && ctx.opts.monthDisplay === "full");
462
+ this.el.classList.toggle("tnc-dg--chips", ctx.opts.monthDisplay === "chips" && this.weeks[0].dots[0].box.clientHeight >= 44);
458
463
  const cs = ctx.win.getComputedStyle(this.el);
459
464
  this.barPx = px(cs, "--tnc-bar-h", 20);
460
465
  this.topPx = this.weeks[0]?.cells[0]?.querySelector(".tnc-dg-top")?.offsetHeight || px(cs, "--tnc-dg-top-h", 24);
461
466
  this.morePx = this.measureMore(cs);
462
467
  this.lastHeights = this.sizeKey();
463
468
  for (const w of this.weeks) {
464
- if (w.dots) this.renderDots(w);
465
- else this.renderWeek(w);
469
+ for (const cell of w.cells) {
470
+ if (this.dotsMode) cell.tabIndex = 0;
471
+ else cell.removeAttribute("tabindex");
472
+ if (this.dotsMode && ctx.opts.monthDisplay === "full") cell.setAttribute("aria-haspopup", "dialog");
473
+ else cell.removeAttribute("aria-haspopup");
474
+ }
475
+ for (const cell of w.dots) cell.box.hidden = !this.dotsMode;
476
+ if (this.dotsMode) {
477
+ w.pool.begin();
478
+ w.pool.end();
479
+ w.more.replaceChildren();
480
+ this.renderDots(w);
481
+ } else {
482
+ for (const cell of w.dots) {
483
+ cell.pool.begin();
484
+ cell.pool.end();
485
+ }
486
+ this.renderWeek(w);
487
+ }
466
488
  }
467
489
  }
468
490
  /** Height of a "+N more" link as the host styles it (`--tnc-more-h`, or a taller min-height). */
@@ -561,7 +583,8 @@ var DayGridView = class {
561
583
  w.more.append(link);
562
584
  }
563
585
  existing.delete(key);
564
- link.textContent = ctx.text.more(items.length);
586
+ link.setAttribute("aria-label", ctx.text.more(items.length));
587
+ link.textContent = w.row.clientWidth / n < 72 ? `+${new Intl.NumberFormat(ctx.opts.locale).format(items.length)}` : ctx.text.more(items.length);
565
588
  setStyle(link, {
566
589
  left: `calc(${pct(col / n)} + 2px)`,
567
590
  width: `calc(${pct(1 / n)} - 4px)`,
@@ -591,10 +614,11 @@ var DayGridView = class {
591
614
  const { dayMaxEvents } = ctx.opts;
592
615
  const first = w.dots[0].box;
593
616
  const cs = ctx.win.getComputedStyle(first);
594
- const size = px(cs, "--tnc-dot-size", 8);
617
+ const chips = this.el.classList.contains("tnc-dg--chips");
618
+ const size = chips ? px(cs, "--tnc-chip-h", 14) : px(cs, "--tnc-dot-size", 8);
595
619
  const gap = px(cs, "--tnc-dot-gap", 3);
596
620
  const step = size + gap;
597
- const perRow = Math.max(1, Math.floor((first.clientWidth + gap) / step));
621
+ const perRow = chips ? 1 : Math.max(1, Math.floor((first.clientWidth + gap) / step));
598
622
  const rows = Math.max(1, Math.floor((first.clientHeight + gap) / step));
599
623
  const fits = dayMaxEvents === false ? Infinity : perRow * rows;
600
624
  const cap = typeof dayMaxEvents === "number" && dayMaxEvents > 0 ? Math.min(dayMaxEvents, fits) : fits;
@@ -603,7 +627,7 @@ var DayGridView = class {
603
627
  w.days.forEach((day, i) => {
604
628
  const cell = w.dots[i];
605
629
  const items = this.itemsOn(day);
606
- const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(0, cap - 2) : cap;
630
+ const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(1, cap - (chips ? 1 : 2)) : Math.max(1, cap);
607
631
  cell.pool.begin();
608
632
  const els = items.slice(0, shown).map((n) => cell.pool.place(n.id, { n, day, isStart: n.startDay >= day, isEnd: n.endDay <= day + 1, variant: "indicator", mirror: false }));
609
633
  cell.pool.end();
@@ -720,6 +744,7 @@ var ListView = class {
720
744
  const leadItems = [];
721
745
  const inLead = /* @__PURE__ */ new Set();
722
746
  const keep = /* @__PURE__ */ new Set();
747
+ let itemCount = 0;
723
748
  let prev = null;
724
749
  for (const day of days) {
725
750
  let items = this.itemsOn(day);
@@ -733,7 +758,7 @@ var ListView = class {
733
758
  return false;
734
759
  });
735
760
  }
736
- if (!items.length && !agenda) continue;
761
+ if (!items.length && !agenda && opts.listDayCount !== 1) continue;
737
762
  keep.add(day);
738
763
  let section = this.sections.get(day);
739
764
  let pool = this.pools.get(day);
@@ -752,6 +777,7 @@ var ListView = class {
752
777
  this.pools.set(day, pool);
753
778
  }
754
779
  section.classList.toggle("is-empty", !items.length);
780
+ itemCount += items.length;
755
781
  this.fill(pool, items, () => day);
756
782
  if (prev ? prev.nextSibling !== section : this.body.firstChild !== section) {
757
783
  if (prev) prev.after(section);
@@ -767,13 +793,14 @@ var ListView = class {
767
793
  this.sections.delete(day);
768
794
  }
769
795
  this.renderLead(leadItems);
770
- let empty = this.body.querySelector(":scope > .tnc-list-empty");
771
- if (!keep.size && !leadItems.length) {
796
+ let empty = this.body.querySelector(".tnc-list-empty");
797
+ if (!itemCount && !leadItems.length && (!agenda || opts.listEmptyContent)) {
772
798
  if (!empty) {
773
799
  empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
774
800
  const content = ctx.opts.listEmptyContent?.();
775
801
  if (content) empty.replaceChildren(content);
776
- this.body.append(empty);
802
+ if (this.sections.size === 1) this.sections.values().next().value.append(empty);
803
+ else this.body.prepend(empty);
777
804
  }
778
805
  } else empty?.remove();
779
806
  this.ctx.strip?.draw();
@@ -831,7 +858,7 @@ var ListView = class {
831
858
  const section = day === this.ctx.days[0] && this.lead ? this.lead.section : this.sections.get(day);
832
859
  if (!section) return;
833
860
  this.ctx.strip?.measure();
834
- section.scrollIntoView({ block: "start" });
861
+ this.scroller.scrollTop += section.getBoundingClientRect().top - this.scroller.getBoundingClientRect().top;
835
862
  this.ctx.strip?.setFocus(day);
836
863
  this.pinned = true;
837
864
  }
@@ -920,8 +947,7 @@ var DateStrip = class {
920
947
  head.append(this.title, this.todayBtn, prev, next, this.slot);
921
948
  this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
922
949
  this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
923
- const handle = h(doc, "button", "tnc-strip-handle", { type: "button", tabindex: "-1", "aria-hidden": "true" });
924
- handle.addEventListener("click", () => this.setExpanded(!this.expanded));
950
+ const handle = h(doc, "div", "tnc-strip-handle", { "aria-hidden": "true" });
925
951
  this.el.append(head, this.dows, this.grid, handle);
926
952
  this.grid.addEventListener("click", this.onClick);
927
953
  this.grid.addEventListener("keydown", this.onKeyDown);
@@ -1500,8 +1526,15 @@ var TimeGridView = class {
1500
1526
  link.classList.toggle("tnc-more--collision", collapse);
1501
1527
  link.textContent = `+${rec.count}`;
1502
1528
  if (collapse) {
1503
- link.textContent = ctx.text.appointments(rec.count);
1504
- link.setAttribute("aria-label", link.textContent);
1529
+ const items = placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.item.title);
1530
+ const count = ctx.text.appointments(rec.count);
1531
+ const interval = `${ctx.fmt.time(rec.start)} \u2013 ${ctx.fmt.time(rec.end)}`;
1532
+ link.replaceChildren(
1533
+ h(ctx.doc, "span", "tnc-collision-count", null, String(rec.count)),
1534
+ h(ctx.doc, "span", "tnc-collision-range", null, interval),
1535
+ h(ctx.doc, "span", "tnc-collision-title", { "aria-hidden": "true" }, items.join(" \xB7 "))
1536
+ );
1537
+ link.setAttribute("aria-label", `${count}, ${interval}: ${items.join(", ")}`);
1505
1538
  link.title = link.getAttribute("aria-label");
1506
1539
  } else {
1507
1540
  link.removeAttribute("aria-label");
@@ -1510,6 +1543,7 @@ var TimeGridView = class {
1510
1543
  const cols = maxStack;
1511
1544
  setStyle(link, {
1512
1545
  top: top(rec.start),
1546
+ height: collapse ? pct((rec.end - rec.start) / this.minutes) : "",
1513
1547
  ...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
1514
1548
  });
1515
1549
  }
@@ -1576,12 +1610,10 @@ var TimeGridView = class {
1576
1610
  const r2 = cells[i2].getBoundingClientRect();
1577
1611
  if (x >= r2.left && x < r2.right) return i2;
1578
1612
  }
1579
- const firstR = cells[0]?.getBoundingClientRect();
1580
- const lastR = cells[cells.length - 1]?.getBoundingClientRect();
1581
- if (firstR && x < firstR.left) return 0;
1582
- if (lastR && x >= lastR.right) return cells.length - 1;
1583
1613
  return -1;
1584
1614
  };
1615
+ const bounds = this.scroller.getBoundingClientRect();
1616
+ if (x < bounds.left || x >= bounds.right || y < bounds.top || y >= bounds.bottom) return null;
1585
1617
  if (this.allDayRow) {
1586
1618
  const r2 = this.allDayRow.getBoundingClientRect();
1587
1619
  if (y >= r2.top && y < r2.bottom || !this.body && y >= r2.top) {
@@ -2304,6 +2336,14 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
2304
2336
  setScroll(s) {
2305
2337
  this.scroller.scrollTop = s.top;
2306
2338
  }
2339
+ revealDay(day) {
2340
+ const month = this.cells.find((cell) => cell.day === day)?.el.closest(".tnc-ym");
2341
+ if (!month) return;
2342
+ const bounds = month.getBoundingClientRect();
2343
+ const viewport = this.scroller.getBoundingClientRect();
2344
+ if (bounds.top < viewport.top) this.scroller.scrollTop += bounds.top - viewport.top;
2345
+ else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.bottom - viewport.bottom;
2346
+ }
2307
2347
  elementsFor(_id) {
2308
2348
  return [];
2309
2349
  }
@@ -2383,6 +2423,11 @@ var Calendar = class {
2383
2423
  conflicts = /* @__PURE__ */ new Map();
2384
2424
  slot = { min: 0, max: MINUTES_PER_DAY, duration: 30, snap: 30, scroll: 480 };
2385
2425
  draggingId = null;
2426
+ listDragPreview = null;
2427
+ dragSources = [];
2428
+ dragScrollFrame = null;
2429
+ dragScrollOrigins = /* @__PURE__ */ new Map();
2430
+ itemWriteStates = /* @__PURE__ */ new Map();
2386
2431
  dragRow = null;
2387
2432
  strip = null;
2388
2433
  stripSize = null;
@@ -2396,6 +2441,7 @@ var Calendar = class {
2396
2441
  session = null;
2397
2442
  selection = null;
2398
2443
  suppressClick = false;
2444
+ cancelledPointerId = null;
2399
2445
  selectedIds = /* @__PURE__ */ new Set();
2400
2446
  popover = null;
2401
2447
  timer = null;
@@ -2413,6 +2459,7 @@ var Calendar = class {
2413
2459
  this.positionMore();
2414
2460
  });
2415
2461
  this.resizeObs.observe(this.root);
2462
+ this.resizeObs.observe(this.toolbarEl);
2416
2463
  }
2417
2464
  }
2418
2465
  onAdoptCheck = () => {
@@ -2539,7 +2586,12 @@ var Calendar = class {
2539
2586
  gotoDate(date) {
2540
2587
  const day = this.toDay(date);
2541
2588
  if (day === null) return;
2589
+ const sameMonth = this.opts.view === "month" && startOfMonth(day) === startOfMonth(this.anchor);
2542
2590
  this.anchor = day;
2591
+ if (sameMonth) {
2592
+ this.view = { ...this.view, anchor: day };
2593
+ return;
2594
+ }
2543
2595
  this.rebuild(true);
2544
2596
  }
2545
2597
  prev() {
@@ -2685,7 +2737,8 @@ var Calendar = class {
2685
2737
  out.push(...classList(this.opts.dayClassNames?.(this.dayArg(day))));
2686
2738
  return out;
2687
2739
  }
2688
- isEditable(n, what) {
2740
+ isEditable(n, what, presentation = false) {
2741
+ if (!presentation && this.itemWriteStates.get(n.id) === "pending") return false;
2689
2742
  if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
2690
2743
  if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
2691
2744
  const it = n.item;
@@ -2725,13 +2778,14 @@ var Calendar = class {
2725
2778
  if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
2726
2779
  if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
2727
2780
  const indicator = variant === "indicator";
2728
- if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
2781
+ if (!preview && !indicator && (this.isEditable(n, "start", true) || this.isEditable(n, "duration", true))) el.classList.add("is-draggable");
2729
2782
  if (this.draggingId === n.id) el.classList.add("is-dragging");
2730
2783
  if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
2731
2784
  el.classList.add(...classList(it.className));
2732
2785
  const arg = this.itemArg(el, meta);
2733
2786
  el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
2734
2787
  el.dataset.id = n.id;
2788
+ this.applyItemWriteState(el, n.id);
2735
2789
  if (it.kind) el.dataset.kind = it.kind;
2736
2790
  const color = it.color ?? it.borderColor;
2737
2791
  if (color) el.style.setProperty("--tnc-c", color);
@@ -2748,6 +2802,7 @@ var Calendar = class {
2748
2802
  const custom = variant === "list" ? this.opts.listItemContent?.(arg) : null;
2749
2803
  const content = indicator ? null : custom ?? this.opts.itemContent?.(arg);
2750
2804
  if (indicator) {
2805
+ if (this.opts.monthDisplay === "chips") el.append(h(this.doc, "span", "tnc-item-title", null, it.title));
2751
2806
  } else if (content != null) {
2752
2807
  const wrap = h(this.doc, "div", "tnc-item-main");
2753
2808
  wrap.append(content);
@@ -2760,6 +2815,7 @@ var Calendar = class {
2760
2815
  return el;
2761
2816
  }
2762
2817
  refreshItemEl(el, meta) {
2818
+ this.applyItemWriteState(el, meta.n.id);
2763
2819
  const time = el.querySelector(":scope .tnc-item-time");
2764
2820
  if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
2765
2821
  el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
@@ -2942,6 +2998,7 @@ var Calendar = class {
2942
2998
  this.current.scrollToTime(this.slot.scroll);
2943
2999
  this.current.revealDay(this.anchor);
2944
3000
  } else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
3001
+ else if (this.current instanceof YearView) this.current.revealDay(this.anchor);
2945
3002
  if (rangeChanged) this.opts.onRangeChange?.(this.view);
2946
3003
  }
2947
3004
  /** Sets the visible days and view info from the anchor. */
@@ -2967,9 +3024,11 @@ var Calendar = class {
2967
3024
  }
2968
3025
  /** The range, run on to the end of the strip's days so their dots are right. */
2969
3026
  dataRangeFor(range) {
2970
- const end = this.strip ? Math.max(range.endDay, this.strip.window().end) : range.endDay;
2971
- if (end === range.endDay) return range;
2972
- return { ...range, endDay: end, end: new Date(toInstant(end, 0, this.zone)) };
3027
+ const window = this.strip?.window();
3028
+ const start = window ? Math.min(range.startDay, window.start) : range.startDay;
3029
+ const end = window ? Math.max(range.endDay, window.end) : range.endDay;
3030
+ if (start === range.startDay && end === range.endDay) return range;
3031
+ return { ...range, startDay: start, start: new Date(toInstant(start, 0, this.zone)), endDay: end, end: new Date(toInstant(end, 0, this.zone)) };
2973
3032
  }
2974
3033
  /** Adds, updates or removes the date strip for the current view. */
2975
3034
  syncStrip(navigated) {
@@ -3016,20 +3075,24 @@ var Calendar = class {
3016
3075
  stripShown() {
3017
3076
  if (!this.strip || this.rebuilding || !this.view) return;
3018
3077
  const next = this.dataRangeFor(this.view.range);
3019
- if (next.endDay === this.view.dataRange.endDay) return;
3078
+ if (next.startDay === this.view.dataRange.startDay && next.endDay === this.view.dataRange.endDay) return;
3020
3079
  this.view = { ...this.view, dataRange: next };
3021
3080
  this.opts.onRangeChange?.(this.view);
3022
3081
  }
3023
3082
  /** What is under a point, the date strip included. */
3024
3083
  hitAt(x, y) {
3084
+ const target = this.doc.elementFromPoint(x, y);
3085
+ if (!target || !this.root.contains(target)) {
3086
+ this.strip?.markTarget(null);
3087
+ return null;
3088
+ }
3025
3089
  const strip = this.strip;
3026
3090
  if (strip) {
3027
- const el = this.doc.elementFromPoint(x, y);
3028
- const day = el ? strip.dayAt(el) : null;
3091
+ const day = strip.dayAt(target);
3029
3092
  strip.markTarget(day !== null && this.draggingId ? day : null);
3030
3093
  if (day !== null) return { kind: "day", day };
3031
3094
  }
3032
- return this.current?.hit(x, y) ?? null;
3095
+ return this.current?.el.contains(target) ? this.current.hit(x, y) : null;
3033
3096
  }
3034
3097
  agendaList() {
3035
3098
  return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
@@ -3143,11 +3206,13 @@ var Calendar = class {
3143
3206
  buttons.style.display = "flex";
3144
3207
  select.style.display = "none";
3145
3208
  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;
3209
+ const availableWidth = Math.min(this.root.clientWidth, this.toolbarEl.clientWidth);
3210
+ const compact = mode === "select" || mode === "auto" && startWidth + buttons.scrollWidth + 32 > availableWidth;
3147
3211
  buttons.hidden = compact;
3148
3212
  select.hidden = !compact;
3149
3213
  buttons.style.display = compact ? "none" : "flex";
3150
3214
  select.style.display = compact ? "" : "none";
3215
+ select.parentElement?.classList.toggle("tnc-toolbar-end--compact", compact);
3151
3216
  }
3152
3217
  applyChange(change) {
3153
3218
  if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
@@ -3212,7 +3277,7 @@ var Calendar = class {
3212
3277
  const s = wallTime(n.startMs, this.zone);
3213
3278
  const start = this.fmt.time(s.minute, true);
3214
3279
  if (variant === "dot" || variant === "bar" || variant === "indicator") return { start: s.day === day ? start : "", end: "" };
3215
- if (n.startMs === n.endMs) return { start, end: "" };
3280
+ if (n.startMs === n.endMs || n.item.timeDisplay === "start") return { start, end: "" };
3216
3281
  return { start, end: this.fmt.time(wallTime(n.endMs, this.zone).minute, true) };
3217
3282
  }
3218
3283
  fillTime(el, n, variant, day) {
@@ -3228,7 +3293,8 @@ var Calendar = class {
3228
3293
  }
3229
3294
  const s = wallTime(n.startMs, this.zone);
3230
3295
  const e = wallTime(n.endMs, this.zone);
3231
- return `${n.item.title}, ${this.fmt.longDate(s.day)} ${this.fmt.time(s.minute)} \u2013 ${this.fmt.time(e.minute)}`;
3296
+ const start = `${n.item.title}, ${this.fmt.longDate(s.day)} ${this.fmt.time(s.minute)}`;
3297
+ return n.item.timeDisplay === "start" || n.startMs === n.endMs ? start : `${start} \u2013 ${this.fmt.time(e.minute)}`;
3232
3298
  }
3233
3299
  defaultContent(meta) {
3234
3300
  const { n, variant, day } = meta;
@@ -3256,7 +3322,7 @@ var Calendar = class {
3256
3322
  addHandles(el, meta) {
3257
3323
  const { n, variant } = meta;
3258
3324
  const ports = variant === "timeline" && !meta.mirror && n.id !== EXTERNAL_ID && !!this.opts.timeline?.onLink;
3259
- if (variant === "dot" || !this.isEditable(n, "duration")) {
3325
+ if (variant === "dot" || !this.isEditable(n, "duration", true)) {
3260
3326
  if (ports) this.addPorts(el, n);
3261
3327
  return;
3262
3328
  }
@@ -3384,6 +3450,10 @@ var Calendar = class {
3384
3450
  return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
3385
3451
  }
3386
3452
  onDocPointerDown = (ev) => {
3453
+ if (this.cancelledPointerId !== null) {
3454
+ this.cancelledPointerId = null;
3455
+ this.listenDoc(false);
3456
+ }
3387
3457
  const t = ev.target;
3388
3458
  if (this.popover && !this.popover.el.contains(t) && !this.popover.anchor.contains(t)) this.closeMore();
3389
3459
  if (this.selection && !this.root.contains(t)) this.unselect();
@@ -3391,6 +3461,7 @@ var Calendar = class {
3391
3461
  onPointerDown = (ev) => {
3392
3462
  if (ev.button !== 0 || this.session) return;
3393
3463
  const target = ev.target;
3464
+ if (target.closest(".tnc-dg--compact .tnc-dg-day")) return;
3394
3465
  if (target.closest("button, a, input, select, textarea, .tnc-toolbar, .tnc-port, [data-tnc-no-drag]")) {
3395
3466
  if (!target.closest(".tnc-item")) return;
3396
3467
  if (target.closest(".tnc-port, [data-tnc-no-drag], input, select, textarea")) return;
@@ -3423,7 +3494,7 @@ var Calendar = class {
3423
3494
  y0: ev.clientY,
3424
3495
  active: false,
3425
3496
  origin,
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,
3497
+ grab: overflowOrigin ? this.current instanceof TimeGridView && !origin?.allDay ? { kind: "time", day: overflowOrigin.day, minute: overflowOrigin.minute } : { kind: "day", day: this.meta.get(itemEl).day } : this.current?.hit(ev.clientX, ev.clientY) ?? null,
3427
3498
  proposal: null,
3428
3499
  denied: false,
3429
3500
  timer: null,
@@ -3448,16 +3519,79 @@ var Calendar = class {
3448
3519
  };
3449
3520
  activate(s) {
3450
3521
  s.active = true;
3522
+ if (s.lastEvent) this.leaveHover(s.lastEvent);
3451
3523
  if (s.timer !== null) this.win.clearTimeout(s.timer);
3452
3524
  s.timer = null;
3453
3525
  if (s.origin) {
3454
3526
  this.draggingId = s.origin.id;
3455
- for (const el of this.elementsFor(s.origin.id)) el.classList.add("is-dragging");
3527
+ for (const el of this.elementsFor(s.origin.id)) {
3528
+ if (this.opts.view !== "list" && s.mode === "move") {
3529
+ const source = el.cloneNode(true);
3530
+ source.classList.remove("is-selected");
3531
+ source.classList.add("tnc-drag-source");
3532
+ source.setAttribute("aria-hidden", "true");
3533
+ source.inert = true;
3534
+ for (const node of [source, ...source.querySelectorAll("[id], [data-id], [data-task-path]")]) {
3535
+ node.removeAttribute("id");
3536
+ node.removeAttribute("data-id");
3537
+ node.removeAttribute("data-task-path");
3538
+ }
3539
+ el.before(source);
3540
+ this.dragSources.push(source);
3541
+ }
3542
+ if (this.opts.view === "list" && !this.listDragPreview && s.mode === "move") {
3543
+ const bounds = el.getBoundingClientRect();
3544
+ const preview = el.cloneNode(true);
3545
+ preview.classList.add("tnc-drag-preview", "is-dragging");
3546
+ preview.removeAttribute("data-id");
3547
+ preview.removeAttribute("data-task-path");
3548
+ preview.setAttribute("aria-hidden", "true");
3549
+ preview.inert = true;
3550
+ preview.style.width = `${bounds.width}px`;
3551
+ this.root.append(preview);
3552
+ this.listDragPreview = { element: preview, offsetX: s.x0 - bounds.left, offsetY: s.y0 - bounds.top, width: bounds.width, height: bounds.height };
3553
+ }
3554
+ el.classList.add("is-dragging");
3555
+ }
3456
3556
  }
3457
3557
  this.closeMore();
3458
3558
  if (s.mode !== "select") this.unselectSilently();
3459
3559
  this.root.classList.add("tnc-dragging", `tnc-dragging-${s.mode}`);
3460
3560
  if (s.lastEvent) this.track(s, s.lastEvent);
3561
+ this.startDragScroll();
3562
+ }
3563
+ startDragScroll() {
3564
+ if (this.dragScrollFrame !== null) return;
3565
+ const tick = () => {
3566
+ this.dragScrollFrame = null;
3567
+ const session = this.session;
3568
+ const pointer = session?.lastEvent;
3569
+ if (!session?.active || !pointer) return;
3570
+ const origin = session.origin ? this.elementsFor(session.origin.id)[0] : this.viewHost;
3571
+ const underPointer = this.doc.elementFromPoint(pointer.clientX, pointer.clientY);
3572
+ let moved = false;
3573
+ if (origin && underPointer && this.root.contains(underPointer)) {
3574
+ for (const axis of ["x", "y"]) {
3575
+ const scroller = scrollerOf(origin, axis);
3576
+ if (!scroller) continue;
3577
+ const viewport = scroller === this.doc.scrollingElement ? { left: 0, top: 0, right: this.win.innerWidth, bottom: this.win.innerHeight } : scroller.getBoundingClientRect();
3578
+ const coordinate = axis === "x" ? pointer.clientX : pointer.clientY;
3579
+ const lower = Math.max(0, axis === "x" ? viewport.left : viewport.top);
3580
+ const upper = Math.min(axis === "x" ? this.win.innerWidth : this.win.innerHeight, axis === "x" ? viewport.right : viewport.bottom);
3581
+ if (coordinate < lower || coordinate > upper) continue;
3582
+ const delta = coordinate < lower + 32 ? -Math.min(12, (lower + 32 - coordinate) / 2) : coordinate > upper - 32 ? Math.min(12, (coordinate - upper + 32) / 2) : 0;
3583
+ if (!delta) continue;
3584
+ if (!this.dragScrollOrigins.has(scroller)) this.dragScrollOrigins.set(scroller, { top: scroller.scrollTop, left: scroller.scrollLeft });
3585
+ const before = axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
3586
+ if (axis === "x") scroller.scrollLeft += delta;
3587
+ else scroller.scrollTop += delta;
3588
+ moved ||= before !== (axis === "x" ? scroller.scrollLeft : scroller.scrollTop);
3589
+ }
3590
+ }
3591
+ if (moved) this.track(session, pointer);
3592
+ this.dragScrollFrame = this.win.requestAnimationFrame(tick);
3593
+ };
3594
+ this.dragScrollFrame = this.win.requestAnimationFrame(tick);
3461
3595
  }
3462
3596
  onTouchMove = (ev) => {
3463
3597
  if (this.session?.active) ev.preventDefault();
@@ -3487,6 +3621,12 @@ var Calendar = class {
3487
3621
  this.track(s, ev);
3488
3622
  };
3489
3623
  onPointerUp = (ev) => {
3624
+ if (ev.pointerId === this.cancelledPointerId) {
3625
+ this.cancelledPointerId = null;
3626
+ this.suppressNextClick();
3627
+ this.listenDoc(false);
3628
+ return;
3629
+ }
3490
3630
  const s = this.session;
3491
3631
  if (!s || ev.pointerId !== s.pointerId) return;
3492
3632
  if (!s.active) {
@@ -3499,28 +3639,49 @@ var Calendar = class {
3499
3639
  return;
3500
3640
  }
3501
3641
  this.suppressNextClick();
3642
+ this.track(s, ev);
3502
3643
  this.endSession(ev, false);
3503
3644
  };
3504
3645
  onPointerCancel = (ev) => {
3646
+ if (ev.pointerId === this.cancelledPointerId) {
3647
+ this.cancelledPointerId = null;
3648
+ this.listenDoc(false);
3649
+ }
3505
3650
  if (this.session && ev.pointerId === this.session.pointerId) this.endSession(null, true);
3506
3651
  };
3507
3652
  onDocKeyDown = (ev) => {
3508
3653
  if (ev.key === "Escape" && this.session) {
3509
3654
  ev.preventDefault();
3510
3655
  ev.stopPropagation();
3656
+ const pointerId = this.session.active ? this.session.pointerId : null;
3511
3657
  this.endSession(null, true);
3658
+ if (pointerId !== null) {
3659
+ this.cancelledPointerId = pointerId;
3660
+ this.listenDoc(true);
3661
+ }
3512
3662
  }
3513
3663
  };
3514
3664
  /** Proposal for the current pointer position. */
3515
3665
  track(s, ev) {
3666
+ if (this.listDragPreview) {
3667
+ const preview = this.listDragPreview;
3668
+ const bounds = this.root.getBoundingClientRect();
3669
+ preview.element.style.left = `${Math.max(8, Math.min(this.win.innerWidth - preview.width - 8, ev.clientX - preview.offsetX)) - bounds.left}px`;
3670
+ preview.element.style.top = `${Math.max(8, Math.min(this.win.innerHeight - preview.height - 8, ev.clientY - preview.offsetY)) - bounds.top}px`;
3671
+ }
3516
3672
  const hit = this.hitAt(ev.clientX, ev.clientY);
3517
3673
  if (s.mode === "external") {
3518
3674
  this.trackExternal(s, hit);
3519
3675
  return;
3520
3676
  }
3521
3677
  if (!s.grab) s.grab = hit;
3522
- if (!hit || !s.grab) return;
3678
+ if (!hit || !s.grab) {
3679
+ this.invalidateProposal(s);
3680
+ return;
3681
+ }
3523
3682
  if (s.mode === "select") {
3683
+ s.denied = false;
3684
+ this.root.classList.remove("tnc-drop-denied");
3524
3685
  const sel = this.selectionFor(s.grab, hit);
3525
3686
  if (!sel) return;
3526
3687
  this.selection = sel;
@@ -3529,25 +3690,40 @@ var Calendar = class {
3529
3690
  }
3530
3691
  const origin = s.origin;
3531
3692
  const p = s.mode === "move" ? this.moveProposal(origin, s.grab, hit) : this.resizeProposal(origin, hit, s.mode === "resize-start");
3532
- if (!p) return;
3693
+ if (!p) {
3694
+ this.invalidateProposal(s);
3695
+ return;
3696
+ }
3533
3697
  const allowed = this.opts.canDrop?.({ item: origin.item, start: new Date(p.startMs), end: new Date(p.endMs), allDay: p.allDay, rowId: p.rowId }) ?? true;
3534
3698
  s.denied = !allowed;
3535
3699
  this.root.classList.toggle("tnc-drop-denied", !allowed);
3536
- if (!allowed || sameProposal(p, s.proposal)) return;
3700
+ if (!allowed) {
3701
+ this.invalidateProposal(s);
3702
+ return;
3703
+ }
3704
+ if (sameProposal(p, s.proposal)) return;
3537
3705
  s.proposal = p;
3538
3706
  this.dragRow = p.rowId !== void 0 ? { id: origin.id, rowId: p.rowId } : null;
3539
- this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
3707
+ if (this.opts.view !== "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
3708
+ }
3709
+ invalidateProposal(s) {
3710
+ const hadProposal = s.proposal !== null;
3711
+ s.proposal = null;
3712
+ s.hit = null;
3713
+ s.denied = true;
3714
+ this.dragRow = null;
3715
+ this.root.classList.add("tnc-drop-denied");
3716
+ this.strip?.markTarget(null);
3717
+ this.unselectSilently();
3718
+ if (s.mode === "external") this.applyChange(this.store.remove([EXTERNAL_ID]));
3719
+ else if (hadProposal && s.origin) this.applyChange(this.store.upsert([s.origin.item]));
3540
3720
  }
3541
3721
  trackExternal(s, raw) {
3542
3722
  const src = s.source;
3543
3723
  const hit = raw?.kind === "timeline" && raw.area !== "track" && raw.area !== "header" ? null : raw;
3544
3724
  s.hit = hit;
3545
3725
  if (!hit) {
3546
- if (s.proposal) {
3547
- s.proposal = null;
3548
- this.applyChange(this.store.remove([EXTERNAL_ID]));
3549
- }
3550
- this.unselectSilently();
3726
+ this.invalidateProposal(s);
3551
3727
  return;
3552
3728
  }
3553
3729
  const dur = src.durationMinutes ?? this.opts.defaultTimedMinutes;
@@ -3566,6 +3742,10 @@ var Calendar = class {
3566
3742
  const allowed = this.opts.canDrop?.({ item: this.externalItem(src, p), start: new Date(p.startMs), end: new Date(p.endMs), allDay: p.allDay, rowId: p.rowId }) ?? true;
3567
3743
  s.denied = !allowed;
3568
3744
  this.root.classList.toggle("tnc-drop-denied", !allowed);
3745
+ if (!allowed) {
3746
+ this.invalidateProposal(s);
3747
+ return;
3748
+ }
3569
3749
  if (sameProposal(p, s.proposal)) return;
3570
3750
  s.proposal = p;
3571
3751
  if (hit.kind === "timeline" && p.rowId === void 0) {
@@ -3758,7 +3938,18 @@ var Calendar = class {
3758
3938
  endSession(ev, cancel) {
3759
3939
  const s = this.session;
3760
3940
  if (!s) return;
3941
+ if (this.dragScrollFrame !== null) this.win.cancelAnimationFrame(this.dragScrollFrame);
3942
+ this.dragScrollFrame = null;
3943
+ if (cancel || s.denied) for (const [scroller, position] of this.dragScrollOrigins) {
3944
+ scroller.scrollTop = position.top;
3945
+ scroller.scrollLeft = position.left;
3946
+ }
3947
+ this.dragScrollOrigins.clear();
3761
3948
  this.session = null;
3949
+ this.listDragPreview?.element.remove();
3950
+ this.listDragPreview = null;
3951
+ for (const source of this.dragSources) source.remove();
3952
+ this.dragSources = [];
3762
3953
  if (s.timer !== null) this.win.clearTimeout(s.timer);
3763
3954
  this.listenDoc(false);
3764
3955
  this.root.classList.remove(
@@ -3827,10 +4018,16 @@ var Calendar = class {
3827
4018
  revert();
3828
4019
  return;
3829
4020
  }
4021
+ if (this.opts.view === "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
3830
4022
  this.flush();
3831
4023
  this.commit(s.mode === "move" ? "drop" : "resize", origin, p, ev, revert);
3832
4024
  }
3833
4025
  commit(kind, origin, p, ev, revert) {
4026
+ let reverted = false;
4027
+ const mark = (state) => {
4028
+ this.itemWriteStates.set(origin.id, state);
4029
+ for (const element of this.elementsFor(origin.id)) this.applyItemWriteState(element, origin.id);
4030
+ };
3834
4031
  const arg = {
3835
4032
  item: origin.item,
3836
4033
  start: new Date(p.startMs),
@@ -3846,19 +4043,32 @@ var Calendar = class {
3846
4043
  jsEvent: ev,
3847
4044
  source: ev && "key" in ev ? "keyboard" : "pointer",
3848
4045
  view: this.view,
3849
- revert
4046
+ revert: () => {
4047
+ reverted = true;
4048
+ revert();
4049
+ mark("failed");
4050
+ }
3850
4051
  };
3851
4052
  const handler = kind === "drop" ? this.opts.onItemDrop : this.opts.onItemResize;
3852
4053
  try {
4054
+ mark("pending");
3853
4055
  const res = handler?.(arg);
3854
4056
  if (res && typeof res.then === "function") {
3855
- Promise.resolve(res).catch(() => revert());
3856
- }
4057
+ void Promise.resolve(res).then(() => {
4058
+ if (!reverted) mark("changed");
4059
+ }, () => arg.revert());
4060
+ } else if (!reverted) mark("changed");
3857
4061
  } catch (err) {
3858
- revert();
4062
+ arg.revert();
3859
4063
  throw err;
3860
4064
  }
3861
4065
  }
4066
+ applyItemWriteState(element, id) {
4067
+ const state = this.itemWriteStates.get(id);
4068
+ for (const name of ["pending", "failed", "changed"]) element.classList.toggle(`is-write-${name}`, state === name);
4069
+ if (state === "pending") element.setAttribute("aria-busy", "true");
4070
+ else element.removeAttribute("aria-busy");
4071
+ }
3862
4072
  /* --------------------------------------------------------- click / keys */
3863
4073
  /** Runs in the capture phase so the click ending a drag never reaches content inside the calendar, React portals included. */
3864
4074
  onClickCapture = (ev) => {
@@ -3868,6 +4078,12 @@ var Calendar = class {
3868
4078
  ev.preventDefault();
3869
4079
  };
3870
4080
  onClick = (ev) => {
4081
+ const compactDay = ev.target.closest?.(".tnc-dg--compact .tnc-dg-day");
4082
+ if (compactDay && this.root.contains(compactDay) && !ev.target.closest(".tnc-navlink")) {
4083
+ const day = parseDay(compactDay.dataset.date ?? "");
4084
+ if (day !== null) this.openMore(day, compactDay, ev);
4085
+ return;
4086
+ }
3871
4087
  const yearDay = ev.target.closest?.(".tnc-ym-day");
3872
4088
  if (yearDay && this.root.contains(yearDay)) {
3873
4089
  const day = parseDay(yearDay.dataset.date ?? "");
@@ -3956,7 +4172,8 @@ var Calendar = class {
3956
4172
  const day = parseDay(target.dataset.date ?? "");
3957
4173
  if (day !== null) {
3958
4174
  ev.preventDefault();
3959
- this.clickSlot({ kind: "day", day }, ev, true);
4175
+ if (target.closest(".tnc-dg--compact")) this.openMore(day, target, ev);
4176
+ else this.clickSlot({ kind: "day", day }, ev, true);
3960
4177
  }
3961
4178
  return;
3962
4179
  }
@@ -4044,9 +4261,13 @@ var Calendar = class {
4044
4261
  const dt = ev.dataTransfer;
4045
4262
  if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
4046
4263
  ev.preventDefault();
4264
+ const hit = this.hitAt(ev.clientX, ev.clientY);
4265
+ if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") {
4266
+ dt.dropEffect = "none";
4267
+ this.unselectSilently();
4268
+ return;
4269
+ }
4047
4270
  dt.dropEffect = "copy";
4048
- const hit = this.current.hit(ev.clientX, ev.clientY);
4049
- if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
4050
4271
  const sel = this.selectionFor(hit, hit);
4051
4272
  if (!sel) return;
4052
4273
  this.selection = sel;
@@ -4060,7 +4281,7 @@ var Calendar = class {
4060
4281
  const dt = ev.dataTransfer;
4061
4282
  if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
4062
4283
  ev.preventDefault();
4063
- const hit = this.current.hit(ev.clientX, ev.clientY);
4284
+ const hit = this.hitAt(ev.clientX, ev.clientY);
4064
4285
  this.unselectSilently();
4065
4286
  if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
4066
4287
  const sel = this.selectionFor(hit, hit);
@@ -4091,7 +4312,7 @@ function scrollerOf(el, axis) {
4091
4312
  return doc.scrollingElement;
4092
4313
  }
4093
4314
  function rangeMoved(prev, next) {
4094
- return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.endDay !== next.dataRange.endDay;
4315
+ return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.startDay !== next.dataRange.startDay || prev.dataRange.endDay !== next.dataRange.endDay;
4095
4316
  }
4096
4317
 
4097
4318
  export {