@tasknotes/calendar 0.1.5 → 0.1.6

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.6
4
+
5
+ ### Changed
6
+
7
+ - Time grids give tracked (`lane: "actual"`) items their narrow strip only where they overlap planned items. Elsewhere, planned and tracked items use the full column width, as before the side lane existed. The strip's width is the `--tnc-actual-w` custom property (default 22%).
8
+ - Reported by @RumiaKitinari in [TaskNotes issue 2494](https://github.com/callumalpass/tasknotes/issues/2494) and @mgreen3 in [TaskNotes issue 2498](https://github.com/callumalpass/tasknotes/issues/2498).
9
+ - The toolbar's previous and next buttons draw their chevrons as icons, which are larger and clearer than the ‹ › glyphs.
10
+
3
11
  ## 0.1.5
4
12
 
5
13
  ### Added
package/dist/calendar.css CHANGED
@@ -23,6 +23,8 @@
23
23
  --tnc-off: color-mix(in srgb, var(--tnc-text) 3.5%, transparent);
24
24
  --tnc-shadow: 0 8px 24px rgb(0 0 0 / 0.16);
25
25
  --tnc-radius: 6px;
26
+ /* Time-grid share for tracked ("actual") items where they overlap planned ones. */
27
+ --tnc-actual-w: 22%;
26
28
  --tnc-font-size: 12px;
27
29
  --tnc-small: 11px;
28
30
  --tnc-bar-h: 20px;
@@ -131,9 +133,21 @@
131
133
  }
132
134
  .tnc-btn-prev,
133
135
  .tnc-btn-next {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ justify-content: center;
134
139
  min-width: 28px;
135
140
  padding: 3px 6px;
136
- font-size: 15px;
141
+ }
142
+ .tnc-btn-prev svg,
143
+ .tnc-btn-next svg {
144
+ width: 16px;
145
+ height: 16px;
146
+ fill: none;
147
+ stroke: currentColor;
148
+ stroke-width: 2;
149
+ stroke-linecap: round;
150
+ stroke-linejoin: round;
137
151
  }
138
152
  .tnc-btn:focus-visible,
139
153
  .tnc-navlink:focus-visible,
@@ -612,31 +626,19 @@
612
626
  .tnc-col.is-today {
613
627
  background-color: color-mix(in srgb, var(--tnc-accent) 5%, transparent);
614
628
  }
629
+ /* Items position themselves within the column; see inLane in timegrid.ts. */
615
630
  .tnc-col-plan,
616
631
  .tnc-col-bg,
617
632
  .tnc-col-actual {
618
633
  position: absolute;
619
- top: 0;
620
- bottom: 0;
621
- }
622
- .tnc-col-bg {
623
- left: 0;
624
- right: 0;
625
- }
626
- .tnc-col-plan {
627
- left: 1px;
628
- right: 8px;
634
+ inset: 0;
629
635
  }
636
+ /* Above the plan layer, so it must not swallow clicks meant for plan items. */
630
637
  .tnc-col-actual {
631
- display: none;
632
- right: 2px;
633
- width: 22%;
634
- }
635
- .tnc-col.has-actual .tnc-col-plan {
636
- right: calc(22% + 4px);
638
+ pointer-events: none;
637
639
  }
638
- .tnc-col.has-actual .tnc-col-actual {
639
- display: block;
640
+ .tnc-col-actual > * {
641
+ pointer-events: auto;
640
642
  }
641
643
  .tnc-now {
642
644
  position: absolute;
package/dist/cjs/core.cjs CHANGED
@@ -520,7 +520,7 @@ function packTimed(segs, minMinutes, order = defaultOrder) {
520
520
  colEnds.push(0);
521
521
  }
522
522
  colEnds[col] = s.end;
523
- const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex };
523
+ const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex, split: false };
524
524
  cluster.push(p);
525
525
  out.push(p);
526
526
  clusterEnd = Math.max(clusterEnd, s.end);
@@ -540,7 +540,24 @@ function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTE
540
540
  else if (n.item.lane === "actual") actual.push(seg);
541
541
  else plan.push(seg);
542
542
  }
543
- return { day, plan: packTimed(plan, minMinutes, order), actual: packTimed(actual, minMinutes, order), background };
543
+ const planPlaced = packTimed(plan, minMinutes, order);
544
+ const actualPlaced = packTimed(actual, minMinutes, order);
545
+ markSplit(planPlaced, actualPlaced, minMinutes);
546
+ markSplit(actualPlaced, planPlaced, minMinutes);
547
+ return { day, plan: planPlaced, actual: actualPlaced, background };
548
+ }
549
+ function markSplit(lane, other, minMinutes) {
550
+ if (!lane.length || !other.length) return;
551
+ const end = (p) => Math.max(p.endMin, p.startMin + minMinutes);
552
+ const ranges = /* @__PURE__ */ new Map();
553
+ for (const p of lane) {
554
+ const r = ranges.get(p.cluster);
555
+ if (r) r[1] = Math.max(r[1], end(p));
556
+ else ranges.set(p.cluster, [p.startMin, end(p)]);
557
+ }
558
+ const split = /* @__PURE__ */ new Set();
559
+ for (const [cluster, [s, e]] of ranges) if (other.some((o) => o.startMin < e && s < end(o))) split.add(cluster);
560
+ for (const p of lane) p.split = split.has(p.cluster);
544
561
  }
545
562
  function layoutRow(items, days, order = defaultOrder) {
546
563
  const first = days[0];
@@ -532,7 +532,7 @@ function packTimed(segs, minMinutes, order = defaultOrder) {
532
532
  colEnds.push(0);
533
533
  }
534
534
  colEnds[col] = s.end;
535
- const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex };
535
+ const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex, split: false };
536
536
  cluster.push(p);
537
537
  out.push(p);
538
538
  clusterEnd = Math.max(clusterEnd, s.end);
@@ -552,7 +552,24 @@ function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTE
552
552
  else if (n.item.lane === "actual") actual.push(seg);
553
553
  else plan.push(seg);
554
554
  }
555
- return { day, plan: packTimed(plan, minMinutes, order), actual: packTimed(actual, minMinutes, order), background };
555
+ const planPlaced = packTimed(plan, minMinutes, order);
556
+ const actualPlaced = packTimed(actual, minMinutes, order);
557
+ markSplit(planPlaced, actualPlaced, minMinutes);
558
+ markSplit(actualPlaced, planPlaced, minMinutes);
559
+ return { day, plan: planPlaced, actual: actualPlaced, background };
560
+ }
561
+ function markSplit(lane, other, minMinutes) {
562
+ if (!lane.length || !other.length) return;
563
+ const end = (p) => Math.max(p.endMin, p.startMin + minMinutes);
564
+ const ranges = /* @__PURE__ */ new Map();
565
+ for (const p of lane) {
566
+ const r = ranges.get(p.cluster);
567
+ if (r) r[1] = Math.max(r[1], end(p));
568
+ else ranges.set(p.cluster, [p.startMin, end(p)]);
569
+ }
570
+ const split = /* @__PURE__ */ new Set();
571
+ for (const [cluster, [s, e]] of ranges) if (other.some((o) => o.startMin < e && s < end(o))) split.add(cluster);
572
+ for (const p of lane) p.split = split.has(p.cluster);
556
573
  }
557
574
  function layoutRow(items, days, order = defaultOrder) {
558
575
  const first = days[0];
@@ -1794,6 +1811,11 @@ var DateStrip = class {
1794
1811
 
1795
1812
  // src/dom/views/timegrid.ts
1796
1813
  var BAR_GAP2 = 2;
1814
+ function inLane(lane, split, left, width) {
1815
+ const [x, w] = !split ? ["1px", "100% - 9px"] : lane === "plan" ? ["1px", "100% - var(--tnc-actual-w) - 5px"] : ["100% - var(--tnc-actual-w) - 2px", "var(--tnc-actual-w)"];
1816
+ const f = (n) => Math.round(n * 1e4) / 1e4;
1817
+ return { left: `calc(${x} + (${w}) * ${f(left)})`, width: `calc((${w}) * ${f(width)} - 1px)` };
1818
+ }
1797
1819
  var TimeGridView = class {
1798
1820
  constructor(ctx) {
1799
1821
  this.ctx = ctx;
@@ -2103,19 +2125,20 @@ var TimeGridView = class {
2103
2125
  c.bg.end();
2104
2126
  const hasActual = layout.actual.length > 0;
2105
2127
  c.col.classList.toggle("has-actual", hasActual);
2106
- this.placeLane(c.plan, layout.plan, c, top, height, true);
2107
- this.placeLane(c.actual, layout.actual, c, top, height, false);
2128
+ this.placeLane(c.plan, layout.plan, c, top, height, "plan");
2129
+ this.placeLane(c.actual, layout.actual, c, top, height, "actual");
2108
2130
  }
2109
- placeLane(pool, placements, c, top, height, withMore) {
2131
+ placeLane(pool, placements, c, top, height, lane) {
2110
2132
  const { ctx } = this;
2111
2133
  const { opts } = ctx;
2134
+ const withMore = lane === "plan";
2112
2135
  const maxStack = opts.eventMaxStack;
2113
2136
  const hiddenByCluster = /* @__PURE__ */ new Map();
2114
2137
  pool.begin();
2115
2138
  for (const p of placements) {
2116
2139
  const preview = p.item.item.preview || p.item.id === ctx.draggingId;
2117
2140
  if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
2118
- const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, items: [] };
2141
+ const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
2119
2142
  rec.count++;
2120
2143
  rec.start = Math.min(rec.start, p.startMin);
2121
2144
  rec.end = Math.max(rec.end, p.endMin);
@@ -2134,8 +2157,7 @@ var TimeGridView = class {
2134
2157
  setStyle(el, {
2135
2158
  top: top(p.startMin),
2136
2159
  height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
2137
- left: pct(left),
2138
- width: `calc(${pct(width)} - 1px)`,
2160
+ ...inLane(lane, p.split, left, width),
2139
2161
  "z-index": String(p.col + 1)
2140
2162
  });
2141
2163
  }
@@ -2147,8 +2169,7 @@ var TimeGridView = class {
2147
2169
  const cols = maxStack;
2148
2170
  setStyle(link, {
2149
2171
  top: top(rec.start),
2150
- left: pct((cols - 1) / cols),
2151
- width: `calc(${pct(1 / cols)} - 1px)`
2172
+ ...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
2152
2173
  });
2153
2174
  link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
2154
2175
  c.more.append(link);
@@ -2934,6 +2955,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
2934
2955
 
2935
2956
  // src/dom/calendar.ts
2936
2957
  var EXTERNAL_ID = "__tnc_external__";
2958
+ var SVG_NS = "http://www.w3.org/2000/svg";
2937
2959
  var MAX_AGENDA_DAYS = 366;
2938
2960
  function sameOption(a, b, depth = 0) {
2939
2961
  if (a === b) return true;
@@ -3702,14 +3724,26 @@ var Calendar = class {
3702
3724
  if (e.icon) b.replaceChildren(e.icon());
3703
3725
  return b;
3704
3726
  });
3727
+ const chevron = (d) => {
3728
+ const svg = this.doc.createElementNS(SVG_NS, "svg");
3729
+ setAttrs(svg, { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false" });
3730
+ const path = this.doc.createElementNS(SVG_NS, "path");
3731
+ path.setAttribute("d", d);
3732
+ svg.append(path);
3733
+ return svg;
3734
+ };
3735
+ const prev = btn("", this.text.prev, "tnc-btn-prev", () => this.prev());
3736
+ prev.append(chevron("m15 18-6-6 6-6"));
3737
+ const next = btn("", this.text.next, "tnc-btn-next", () => this.next());
3738
+ next.append(chevron("m9 18 6-6-6-6"));
3705
3739
  const nav = h(
3706
3740
  this.doc,
3707
3741
  "div",
3708
3742
  "tnc-toolbar-group tnc-toolbar-nav",
3709
3743
  null,
3710
- btn("\u2039", this.text.prev, "tnc-btn-prev", () => this.prev()),
3744
+ prev,
3711
3745
  btn(this.text.today, this.text.today, "tnc-btn-today", () => this.goToday()),
3712
- btn("\u203A", this.text.next, "tnc-btn-next", () => this.next())
3746
+ next
3713
3747
  );
3714
3748
  const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
3715
3749
  const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
@@ -859,7 +859,7 @@ function packTimed(segs, minMinutes, order = defaultOrder) {
859
859
  colEnds.push(0);
860
860
  }
861
861
  colEnds[col] = s.end;
862
- const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex };
862
+ const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex, split: false };
863
863
  cluster.push(p);
864
864
  out.push(p);
865
865
  clusterEnd = Math.max(clusterEnd, s.end);
@@ -879,7 +879,24 @@ function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTE
879
879
  else if (n.item.lane === "actual") actual.push(seg);
880
880
  else plan.push(seg);
881
881
  }
882
- return { day, plan: packTimed(plan, minMinutes, order), actual: packTimed(actual, minMinutes, order), background };
882
+ const planPlaced = packTimed(plan, minMinutes, order);
883
+ const actualPlaced = packTimed(actual, minMinutes, order);
884
+ markSplit(planPlaced, actualPlaced, minMinutes);
885
+ markSplit(actualPlaced, planPlaced, minMinutes);
886
+ return { day, plan: planPlaced, actual: actualPlaced, background };
887
+ }
888
+ function markSplit(lane, other, minMinutes) {
889
+ if (!lane.length || !other.length) return;
890
+ const end = (p) => Math.max(p.endMin, p.startMin + minMinutes);
891
+ const ranges = /* @__PURE__ */ new Map();
892
+ for (const p of lane) {
893
+ const r = ranges.get(p.cluster);
894
+ if (r) r[1] = Math.max(r[1], end(p));
895
+ else ranges.set(p.cluster, [p.startMin, end(p)]);
896
+ }
897
+ const split = /* @__PURE__ */ new Set();
898
+ for (const [cluster, [s, e]] of ranges) if (other.some((o) => o.startMin < e && s < end(o))) split.add(cluster);
899
+ for (const p of lane) p.split = split.has(p.cluster);
883
900
  }
884
901
  function layoutRow(items, days, order = defaultOrder) {
885
902
  const first = days[0];
@@ -1702,6 +1719,11 @@ var DateStrip = class {
1702
1719
 
1703
1720
  // src/dom/views/timegrid.ts
1704
1721
  var BAR_GAP2 = 2;
1722
+ function inLane(lane, split, left, width) {
1723
+ const [x, w] = !split ? ["1px", "100% - 9px"] : lane === "plan" ? ["1px", "100% - var(--tnc-actual-w) - 5px"] : ["100% - var(--tnc-actual-w) - 2px", "var(--tnc-actual-w)"];
1724
+ const f = (n) => Math.round(n * 1e4) / 1e4;
1725
+ return { left: `calc(${x} + (${w}) * ${f(left)})`, width: `calc((${w}) * ${f(width)} - 1px)` };
1726
+ }
1705
1727
  var TimeGridView = class {
1706
1728
  constructor(ctx) {
1707
1729
  this.ctx = ctx;
@@ -2011,19 +2033,20 @@ var TimeGridView = class {
2011
2033
  c.bg.end();
2012
2034
  const hasActual = layout.actual.length > 0;
2013
2035
  c.col.classList.toggle("has-actual", hasActual);
2014
- this.placeLane(c.plan, layout.plan, c, top, height, true);
2015
- this.placeLane(c.actual, layout.actual, c, top, height, false);
2036
+ this.placeLane(c.plan, layout.plan, c, top, height, "plan");
2037
+ this.placeLane(c.actual, layout.actual, c, top, height, "actual");
2016
2038
  }
2017
- placeLane(pool, placements, c, top, height, withMore) {
2039
+ placeLane(pool, placements, c, top, height, lane) {
2018
2040
  const { ctx } = this;
2019
2041
  const { opts } = ctx;
2042
+ const withMore = lane === "plan";
2020
2043
  const maxStack = opts.eventMaxStack;
2021
2044
  const hiddenByCluster = /* @__PURE__ */ new Map();
2022
2045
  pool.begin();
2023
2046
  for (const p of placements) {
2024
2047
  const preview = p.item.item.preview || p.item.id === ctx.draggingId;
2025
2048
  if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
2026
- const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, items: [] };
2049
+ const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
2027
2050
  rec.count++;
2028
2051
  rec.start = Math.min(rec.start, p.startMin);
2029
2052
  rec.end = Math.max(rec.end, p.endMin);
@@ -2042,8 +2065,7 @@ var TimeGridView = class {
2042
2065
  setStyle(el, {
2043
2066
  top: top(p.startMin),
2044
2067
  height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
2045
- left: pct(left),
2046
- width: `calc(${pct(width)} - 1px)`,
2068
+ ...inLane(lane, p.split, left, width),
2047
2069
  "z-index": String(p.col + 1)
2048
2070
  });
2049
2071
  }
@@ -2055,8 +2077,7 @@ var TimeGridView = class {
2055
2077
  const cols = maxStack;
2056
2078
  setStyle(link, {
2057
2079
  top: top(rec.start),
2058
- left: pct((cols - 1) / cols),
2059
- width: `calc(${pct(1 / cols)} - 1px)`
2080
+ ...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
2060
2081
  });
2061
2082
  link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
2062
2083
  c.more.append(link);
@@ -2842,6 +2863,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
2842
2863
 
2843
2864
  // src/dom/calendar.ts
2844
2865
  var EXTERNAL_ID = "__tnc_external__";
2866
+ var SVG_NS = "http://www.w3.org/2000/svg";
2845
2867
  var MAX_AGENDA_DAYS = 366;
2846
2868
  function sameOption(a, b, depth = 0) {
2847
2869
  if (a === b) return true;
@@ -3610,14 +3632,26 @@ var Calendar = class {
3610
3632
  if (e.icon) b.replaceChildren(e.icon());
3611
3633
  return b;
3612
3634
  });
3635
+ const chevron = (d) => {
3636
+ const svg = this.doc.createElementNS(SVG_NS, "svg");
3637
+ setAttrs(svg, { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false" });
3638
+ const path = this.doc.createElementNS(SVG_NS, "path");
3639
+ path.setAttribute("d", d);
3640
+ svg.append(path);
3641
+ return svg;
3642
+ };
3643
+ const prev = btn("", this.text.prev, "tnc-btn-prev", () => this.prev());
3644
+ prev.append(chevron("m15 18-6-6 6-6"));
3645
+ const next = btn("", this.text.next, "tnc-btn-next", () => this.next());
3646
+ next.append(chevron("m9 18 6-6-6-6"));
3613
3647
  const nav = h(
3614
3648
  this.doc,
3615
3649
  "div",
3616
3650
  "tnc-toolbar-group tnc-toolbar-nav",
3617
3651
  null,
3618
- btn("\u2039", this.text.prev, "tnc-btn-prev", () => this.prev()),
3652
+ prev,
3619
3653
  btn(this.text.today, this.text.today, "tnc-btn-today", () => this.goToday()),
3620
- btn("\u203A", this.text.next, "tnc-btn-next", () => this.next())
3654
+ next
3621
3655
  );
3622
3656
  const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
3623
3657
  const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
@@ -455,7 +455,7 @@ function packTimed(segs, minMinutes, order = defaultOrder) {
455
455
  colEnds.push(0);
456
456
  }
457
457
  colEnds[col] = s.end;
458
- const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex };
458
+ const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex, split: false };
459
459
  cluster.push(p);
460
460
  out.push(p);
461
461
  clusterEnd = Math.max(clusterEnd, s.end);
@@ -475,7 +475,24 @@ function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTE
475
475
  else if (n.item.lane === "actual") actual.push(seg);
476
476
  else plan.push(seg);
477
477
  }
478
- return { day, plan: packTimed(plan, minMinutes, order), actual: packTimed(actual, minMinutes, order), background };
478
+ const planPlaced = packTimed(plan, minMinutes, order);
479
+ const actualPlaced = packTimed(actual, minMinutes, order);
480
+ markSplit(planPlaced, actualPlaced, minMinutes);
481
+ markSplit(actualPlaced, planPlaced, minMinutes);
482
+ return { day, plan: planPlaced, actual: actualPlaced, background };
483
+ }
484
+ function markSplit(lane, other, minMinutes) {
485
+ if (!lane.length || !other.length) return;
486
+ const end = (p) => Math.max(p.endMin, p.startMin + minMinutes);
487
+ const ranges = /* @__PURE__ */ new Map();
488
+ for (const p of lane) {
489
+ const r = ranges.get(p.cluster);
490
+ if (r) r[1] = Math.max(r[1], end(p));
491
+ else ranges.set(p.cluster, [p.startMin, end(p)]);
492
+ }
493
+ const split = /* @__PURE__ */ new Set();
494
+ for (const [cluster, [s, e]] of ranges) if (other.some((o) => o.startMin < e && s < end(o))) split.add(cluster);
495
+ for (const p of lane) p.split = split.has(p.cluster);
479
496
  }
480
497
  function layoutRow(items, days, order = defaultOrder) {
481
498
  const first = days[0];
@@ -27,7 +27,7 @@ import {
27
27
  wallTime,
28
28
  weekdayOf,
29
29
  ymdOfDay
30
- } from "./chunk-DVMZ5OXN.js";
30
+ } from "./chunk-66A6EMCU.js";
31
31
 
32
32
  // src/dom/format.ts
33
33
  var Formatter = class {
@@ -1096,6 +1096,11 @@ var DateStrip = class {
1096
1096
 
1097
1097
  // src/dom/views/timegrid.ts
1098
1098
  var BAR_GAP2 = 2;
1099
+ function inLane(lane, split, left, width) {
1100
+ const [x, w] = !split ? ["1px", "100% - 9px"] : lane === "plan" ? ["1px", "100% - var(--tnc-actual-w) - 5px"] : ["100% - var(--tnc-actual-w) - 2px", "var(--tnc-actual-w)"];
1101
+ const f = (n) => Math.round(n * 1e4) / 1e4;
1102
+ return { left: `calc(${x} + (${w}) * ${f(left)})`, width: `calc((${w}) * ${f(width)} - 1px)` };
1103
+ }
1099
1104
  var TimeGridView = class {
1100
1105
  constructor(ctx) {
1101
1106
  this.ctx = ctx;
@@ -1405,19 +1410,20 @@ var TimeGridView = class {
1405
1410
  c.bg.end();
1406
1411
  const hasActual = layout.actual.length > 0;
1407
1412
  c.col.classList.toggle("has-actual", hasActual);
1408
- this.placeLane(c.plan, layout.plan, c, top, height, true);
1409
- this.placeLane(c.actual, layout.actual, c, top, height, false);
1413
+ this.placeLane(c.plan, layout.plan, c, top, height, "plan");
1414
+ this.placeLane(c.actual, layout.actual, c, top, height, "actual");
1410
1415
  }
1411
- placeLane(pool, placements, c, top, height, withMore) {
1416
+ placeLane(pool, placements, c, top, height, lane) {
1412
1417
  const { ctx } = this;
1413
1418
  const { opts } = ctx;
1419
+ const withMore = lane === "plan";
1414
1420
  const maxStack = opts.eventMaxStack;
1415
1421
  const hiddenByCluster = /* @__PURE__ */ new Map();
1416
1422
  pool.begin();
1417
1423
  for (const p of placements) {
1418
1424
  const preview = p.item.item.preview || p.item.id === ctx.draggingId;
1419
1425
  if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
1420
- const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, items: [] };
1426
+ const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
1421
1427
  rec.count++;
1422
1428
  rec.start = Math.min(rec.start, p.startMin);
1423
1429
  rec.end = Math.max(rec.end, p.endMin);
@@ -1436,8 +1442,7 @@ var TimeGridView = class {
1436
1442
  setStyle(el, {
1437
1443
  top: top(p.startMin),
1438
1444
  height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
1439
- left: pct(left),
1440
- width: `calc(${pct(width)} - 1px)`,
1445
+ ...inLane(lane, p.split, left, width),
1441
1446
  "z-index": String(p.col + 1)
1442
1447
  });
1443
1448
  }
@@ -1449,8 +1454,7 @@ var TimeGridView = class {
1449
1454
  const cols = maxStack;
1450
1455
  setStyle(link, {
1451
1456
  top: top(rec.start),
1452
- left: pct((cols - 1) / cols),
1453
- width: `calc(${pct(1 / cols)} - 1px)`
1457
+ ...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
1454
1458
  });
1455
1459
  link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
1456
1460
  c.more.append(link);
@@ -2236,6 +2240,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
2236
2240
 
2237
2241
  // src/dom/calendar.ts
2238
2242
  var EXTERNAL_ID = "__tnc_external__";
2243
+ var SVG_NS = "http://www.w3.org/2000/svg";
2239
2244
  var MAX_AGENDA_DAYS = 366;
2240
2245
  function sameOption(a, b, depth = 0) {
2241
2246
  if (a === b) return true;
@@ -3004,14 +3009,26 @@ var Calendar = class {
3004
3009
  if (e.icon) b.replaceChildren(e.icon());
3005
3010
  return b;
3006
3011
  });
3012
+ const chevron = (d) => {
3013
+ const svg = this.doc.createElementNS(SVG_NS, "svg");
3014
+ setAttrs(svg, { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false" });
3015
+ const path = this.doc.createElementNS(SVG_NS, "path");
3016
+ path.setAttribute("d", d);
3017
+ svg.append(path);
3018
+ return svg;
3019
+ };
3020
+ const prev = btn("", this.text.prev, "tnc-btn-prev", () => this.prev());
3021
+ prev.append(chevron("m15 18-6-6 6-6"));
3022
+ const next = btn("", this.text.next, "tnc-btn-next", () => this.next());
3023
+ next.append(chevron("m9 18 6-6-6-6"));
3007
3024
  const nav = h(
3008
3025
  this.doc,
3009
3026
  "div",
3010
3027
  "tnc-toolbar-group tnc-toolbar-nav",
3011
3028
  null,
3012
- btn("\u2039", this.text.prev, "tnc-btn-prev", () => this.prev()),
3029
+ prev,
3013
3030
  btn(this.text.today, this.text.today, "tnc-btn-today", () => this.goToday()),
3014
- btn("\u203A", this.text.next, "tnc-btn-next", () => this.next())
3031
+ next
3015
3032
  );
3016
3033
  const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
3017
3034
  const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
package/dist/esm/core.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  wallTime,
41
41
  weekdayOf,
42
42
  ymdOfDay
43
- } from "./chunk-DVMZ5OXN.js";
43
+ } from "./chunk-66A6EMCU.js";
44
44
  export {
45
45
  DAY_MS,
46
46
  ItemStore,
package/dist/esm/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  TIMELINE_UNIT_DAYS,
8
8
  defaultOptions,
9
9
  localeFirstDay
10
- } from "./chunk-25TY2UVR.js";
10
+ } from "./chunk-URE7QLU3.js";
11
11
  import {
12
12
  DAY_MS,
13
13
  ItemStore,
@@ -49,7 +49,7 @@ import {
49
49
  wallTime,
50
50
  weekdayOf,
51
51
  ymdOfDay
52
- } from "./chunk-DVMZ5OXN.js";
52
+ } from "./chunk-66A6EMCU.js";
53
53
 
54
54
  // src/tasknotes/bases.ts
55
55
  var FROM_BASES = {
package/dist/esm/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Calendar
3
- } from "./chunk-25TY2UVR.js";
4
- import "./chunk-DVMZ5OXN.js";
3
+ } from "./chunk-URE7QLU3.js";
4
+ import "./chunk-66A6EMCU.js";
5
5
 
6
6
  // src/react/index.tsx
7
7
  import { useEffect, useLayoutEffect, useRef } from "react";
@@ -19,6 +19,11 @@ export interface TimedPlacement<D = unknown> extends TimedSegment<D> {
19
19
  span: number;
20
20
  /** Index of the overlap cluster within the day. */
21
21
  cluster: number;
22
+ /**
23
+ * Whether the cluster overlaps an item in the other lane (plan vs actual).
24
+ * Only then does the item keep to its lane's share of the column.
25
+ */
26
+ split: boolean;
22
27
  }
23
28
  export interface TimedDayLayout<D = unknown> {
24
29
  day: Day;
@@ -19,6 +19,11 @@ export interface TimedPlacement<D = unknown> extends TimedSegment<D> {
19
19
  span: number;
20
20
  /** Index of the overlap cluster within the day. */
21
21
  cluster: number;
22
+ /**
23
+ * Whether the cluster overlaps an item in the other lane (plan vs actual).
24
+ * Only then does the item keep to its lane's share of the column.
25
+ */
26
+ split: boolean;
22
27
  }
23
28
  export interface TimedDayLayout<D = unknown> {
24
29
  day: Day;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tasknotes/calendar",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Task-native calendar engine for TaskNotes: incremental layout, time grid, month, list, year and timeline views.",
5
5
  "license": "MIT",
6
6
  "type": "module",