@tasknotes/calendar 0.1.4 → 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,24 @@
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
+
11
+ ## 0.1.5
12
+
13
+ ### Added
14
+
15
+ - Host-managed `CalendarItem.preview` placements for group drags. They use mirror styling, are non-interactive and hidden from assistive navigation, and remain visible beyond month/all-day event limits and timed stack limits. Multiple overflow previews stack separately.
16
+ - Supports the TaskNotes batch-scheduling workflow requested by @mnicolas94 in [Discussion 2435](https://github.com/callumalpass/tasknotes/discussions/2435), with the existing limitations clarified by @Thanatos9404.
17
+
18
+ ### Fixed
19
+
20
+ - The dragged anchor remains visible when moving into a timed cluster limited by `eventMaxStack`.
21
+
3
22
  ## 0.1.4
4
23
 
5
24
  ### Added
package/README.md CHANGED
@@ -41,6 +41,7 @@ The host persists changes; the calendar supplies optimistic updates and a
41
41
  - **Items** take `start`/`end` as `YYYY-MM-DD` (all-day) or `YYYY-MM-DDTHH:mm` (floating, read in the calendar's zone), ISO strings with an offset, or epoch ms. `end` is exclusive. Optional fields: `allDay`, `color`, `kind`, `lane` (`"plan"`/`"actual"`), `display: "background"`, `completed`, `dependsOn`, `rev` (cheap change signature), `data`.
42
42
  - **Updates** are incremental: `setItems` diffs against the previous set, and `upsertItems`/`removeItems` re-render only the days touched. Render hooks (`itemDidMount`, …) fire only for elements that actually change, but every callback's `item` is the object most recently passed in for that id, so `item.data` is never stale.
43
43
  - **Render hooks** (`itemContent`, `itemClassNames`, `itemDidMount`, `itemWillUnmount`, `listItemContent`) get `variant` (`"timed"`, `"bar"`, `"dot"`, `"indicator"`, `"list"`, `"timeline"`) and `timeText`, the time label in the calendar's locale and hour format (`"9am – 10:30am"` for timed, list and timeline items, the start for bars and dots, empty for all-day items).
44
+ - **Preview items**: set `preview: true` on a temporary item passed through `upsertItems` to show a host-managed group-drag placement. Previews have mirror styling, are hidden from assistive navigation, cannot be activated or edited, and remain visible beyond month/all-day event limits and timed stack limits. Multiple overflow previews stack without covering each other. `itemDidMount` reports `isMirror: true`. Restore the original item (or remove the preview ID) on drop, cancellation, source refresh and teardown; this flag does not persist or automatically commit a group.
44
45
  - **Drags** move the item itself with the pointer in every view (time grid, all-day row, month, timeline), for mouse and touch; the dragged item is never folded into "+N more".
45
46
  - **Changes** (`onItemDrop`, `onItemResize`, `onSelect`, `onExternalDrop`) report TaskNotes storage strings (`startStr`/`endStr`), `revert()`, and `source: "pointer" | "keyboard"`. Returning a rejected promise reverts.
46
47
  - **Clicks**: `onDateClick` reports `minute` (the slot's wall-clock minute, snapped to `slotDuration`; null for day cells and the all-day row), `allDay` and `startStr` (`"YYYY-MM-DD"` or `"YYYY-MM-DDTHH:mm"`), so a tap on a slot can prefill a time.
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,
@@ -224,9 +238,10 @@
224
238
  opacity: 0.92;
225
239
  }
226
240
  .tnc-item.is-mirror {
241
+ z-index: 40 !important;
227
242
  opacity: 0.8;
228
243
  border-style: dashed !important;
229
- pointer-events: none;
244
+ pointer-events: none !important;
230
245
  }
231
246
 
232
247
  /* timed (time grid) */
@@ -611,31 +626,19 @@
611
626
  .tnc-col.is-today {
612
627
  background-color: color-mix(in srgb, var(--tnc-accent) 5%, transparent);
613
628
  }
629
+ /* Items position themselves within the column; see inLane in timegrid.ts. */
614
630
  .tnc-col-plan,
615
631
  .tnc-col-bg,
616
632
  .tnc-col-actual {
617
633
  position: absolute;
618
- top: 0;
619
- bottom: 0;
620
- }
621
- .tnc-col-bg {
622
- left: 0;
623
- right: 0;
624
- }
625
- .tnc-col-plan {
626
- left: 1px;
627
- right: 8px;
634
+ inset: 0;
628
635
  }
636
+ /* Above the plan layer, so it must not swallow clicks meant for plan items. */
629
637
  .tnc-col-actual {
630
- display: none;
631
- right: 2px;
632
- width: 22%;
633
- }
634
- .tnc-col.has-actual .tnc-col-plan {
635
- right: calc(22% + 4px);
638
+ pointer-events: none;
636
639
  }
637
- .tnc-col.has-actual .tnc-col-actual {
638
- display: block;
640
+ .tnc-col-actual > * {
641
+ pointer-events: auto;
639
642
  }
640
643
  .tnc-now {
641
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];
@@ -1210,17 +1227,22 @@ var DayGridView = class {
1210
1227
  const n = w.days.length;
1211
1228
  const hidden = new Array(n).fill([]).map(() => []);
1212
1229
  w.pool.begin();
1230
+ const previewLevels = new Array(n).fill(0);
1213
1231
  let maxLevel = -1;
1214
1232
  for (const p of row.placements) {
1215
1233
  let level = p.level;
1216
1234
  let room = Infinity;
1217
1235
  for (let c = p.startCol; c < p.endCol; c++) room = Math.min(room, visible(c));
1218
1236
  if (level >= room) {
1219
- if (p.item.id !== ctx.draggingId) {
1237
+ if (!p.item.item.preview && p.item.id !== ctx.draggingId) {
1220
1238
  for (let c = p.startCol; c < p.endCol; c++) hidden[c].push(p.item);
1221
1239
  continue;
1222
1240
  }
1223
- level = Math.max(0, room - 1);
1241
+ if (p.item.item.preview) {
1242
+ level = 0;
1243
+ for (let c = p.startCol; c < p.endCol; c++) level = Math.max(level, visible(c) + 1, previewLevels[c]);
1244
+ for (let c = p.startCol; c < p.endCol; c++) previewLevels[c] = level + 1;
1245
+ } else level = Math.max(0, room - 1);
1224
1246
  }
1225
1247
  maxLevel = Math.max(maxLevel, level);
1226
1248
  const single = !p.item.allDay && p.item.endDay - p.item.startDay <= 1;
@@ -1789,6 +1811,11 @@ var DateStrip = class {
1789
1811
 
1790
1812
  // src/dom/views/timegrid.ts
1791
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
+ }
1792
1819
  var TimeGridView = class {
1793
1820
  constructor(ctx) {
1794
1821
  this.ctx = ctx;
@@ -2005,17 +2032,22 @@ var TimeGridView = class {
2005
2032
  const pool = this.allDayPool;
2006
2033
  pool.begin();
2007
2034
  const hidden = new Array(days.length).fill(0);
2035
+ const previewLevels = new Array(days.length).fill(0);
2008
2036
  let maxLevel = -1;
2009
2037
  for (const p of row.placements) {
2010
2038
  let level = p.level;
2011
2039
  let room = Infinity;
2012
2040
  for (let c = p.startCol; c < p.endCol; c++) room = Math.min(room, visibleLevels(c));
2013
2041
  if (level >= room) {
2014
- if (p.item.id !== ctx.draggingId) {
2042
+ if (!p.item.item.preview && p.item.id !== ctx.draggingId) {
2015
2043
  for (let c = p.startCol; c < p.endCol; c++) hidden[c]++;
2016
2044
  continue;
2017
2045
  }
2018
- level = Math.max(0, room - 1);
2046
+ if (p.item.item.preview) {
2047
+ level = 0;
2048
+ for (let c = p.startCol; c < p.endCol; c++) level = Math.max(level, visibleLevels(c) + 1, previewLevels[c]);
2049
+ for (let c = p.startCol; c < p.endCol; c++) previewLevels[c] = level + 1;
2050
+ } else level = Math.max(0, room - 1);
2019
2051
  }
2020
2052
  maxLevel = Math.max(maxLevel, level);
2021
2053
  const el = pool.place(p.item.id, {
@@ -2093,18 +2125,20 @@ var TimeGridView = class {
2093
2125
  c.bg.end();
2094
2126
  const hasActual = layout.actual.length > 0;
2095
2127
  c.col.classList.toggle("has-actual", hasActual);
2096
- this.placeLane(c.plan, layout.plan, c, top, height, true);
2097
- 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");
2098
2130
  }
2099
- placeLane(pool, placements, c, top, height, withMore) {
2131
+ placeLane(pool, placements, c, top, height, lane) {
2100
2132
  const { ctx } = this;
2101
2133
  const { opts } = ctx;
2134
+ const withMore = lane === "plan";
2102
2135
  const maxStack = opts.eventMaxStack;
2103
2136
  const hiddenByCluster = /* @__PURE__ */ new Map();
2104
2137
  pool.begin();
2105
2138
  for (const p of placements) {
2106
- if (withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
2107
- const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, items: [] };
2139
+ const preview = p.item.item.preview || p.item.id === ctx.draggingId;
2140
+ if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
2141
+ const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
2108
2142
  rec.count++;
2109
2143
  rec.start = Math.min(rec.start, p.startMin);
2110
2144
  rec.end = Math.max(rec.end, p.endMin);
@@ -2112,7 +2146,7 @@ var TimeGridView = class {
2112
2146
  hiddenByCluster.set(p.cluster, rec);
2113
2147
  continue;
2114
2148
  }
2115
- const cols = withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
2149
+ const cols = !preview && withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
2116
2150
  const span = Math.min(p.span, cols - p.col);
2117
2151
  const left = p.col / cols;
2118
2152
  let width = span / cols;
@@ -2123,8 +2157,7 @@ var TimeGridView = class {
2123
2157
  setStyle(el, {
2124
2158
  top: top(p.startMin),
2125
2159
  height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
2126
- left: pct(left),
2127
- width: `calc(${pct(width)} - 1px)`,
2160
+ ...inLane(lane, p.split, left, width),
2128
2161
  "z-index": String(p.col + 1)
2129
2162
  });
2130
2163
  }
@@ -2136,8 +2169,7 @@ var TimeGridView = class {
2136
2169
  const cols = maxStack;
2137
2170
  setStyle(link, {
2138
2171
  top: top(rec.start),
2139
- left: pct((cols - 1) / cols),
2140
- width: `calc(${pct(1 / cols)} - 1px)`
2172
+ ...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
2141
2173
  });
2142
2174
  link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
2143
2175
  c.more.append(link);
@@ -2923,6 +2955,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
2923
2955
 
2924
2956
  // src/dom/calendar.ts
2925
2957
  var EXTERNAL_ID = "__tnc_external__";
2958
+ var SVG_NS = "http://www.w3.org/2000/svg";
2926
2959
  var MAX_AGENDA_DAYS = 366;
2927
2960
  function sameOption(a, b, depth = 0) {
2928
2961
  if (a === b) return true;
@@ -3283,7 +3316,7 @@ var Calendar = class {
3283
3316
  return out;
3284
3317
  }
3285
3318
  isEditable(n, what) {
3286
- if (n.id === EXTERNAL_ID || this.opts.view === "year") return false;
3319
+ if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
3287
3320
  if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
3288
3321
  const it = n.item;
3289
3322
  const base = it.editable ?? this.opts.editable;
@@ -3309,6 +3342,7 @@ var Calendar = class {
3309
3342
  createItemEl(meta) {
3310
3343
  const { n, variant } = meta;
3311
3344
  const it = n.item;
3345
+ const preview = meta.mirror || it.preview === true;
3312
3346
  const el = h(this.doc, "div", "tnc-item");
3313
3347
  el.classList.add(`tnc-item--${variant}`);
3314
3348
  if (it.kind) el.classList.add(`tnc-k-${it.kind.replace(/[^\w-]/g, "-")}`);
@@ -3318,12 +3352,12 @@ var Calendar = class {
3318
3352
  if (!meta.isStart) el.classList.add("is-cont-start");
3319
3353
  if (!meta.isEnd) el.classList.add("is-cont-end");
3320
3354
  if (n.allDay) el.classList.add("is-all-day");
3321
- if (meta.mirror || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
3355
+ if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
3322
3356
  if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
3323
3357
  const indicator = variant === "indicator";
3324
- if (!meta.mirror && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
3358
+ if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
3325
3359
  if (this.draggingId === n.id) el.classList.add("is-dragging");
3326
- if (this.selectedIds.has(n.id) && !meta.mirror) el.classList.add("is-selected");
3360
+ if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
3327
3361
  el.classList.add(...classList(it.className));
3328
3362
  const arg = this.itemArg(el, meta);
3329
3363
  el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
@@ -3334,7 +3368,7 @@ var Calendar = class {
3334
3368
  if (it.backgroundColor) el.style.setProperty("--tnc-bg", it.backgroundColor);
3335
3369
  if (it.borderColor) el.style.setProperty("--tnc-border", it.borderColor);
3336
3370
  if (it.textColor) el.style.setProperty("--tnc-fg", it.textColor);
3337
- if (indicator) el.setAttribute("aria-hidden", "true");
3371
+ if (indicator || preview) el.setAttribute("aria-hidden", "true");
3338
3372
  else if (it.display !== "background") {
3339
3373
  el.tabIndex = 0;
3340
3374
  el.setAttribute("role", "button");
@@ -3351,7 +3385,7 @@ var Calendar = class {
3351
3385
  } else {
3352
3386
  el.append(this.defaultContent(meta));
3353
3387
  }
3354
- if (variant !== "list" && !indicator && it.display !== "background") this.addHandles(el, meta);
3388
+ if (!preview && variant !== "list" && !indicator && it.display !== "background") this.addHandles(el, meta);
3355
3389
  this.opts.itemDidMount?.(arg);
3356
3390
  return el;
3357
3391
  }
@@ -3360,7 +3394,7 @@ var Calendar = class {
3360
3394
  if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
3361
3395
  el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
3362
3396
  el.classList.toggle("is-conflict", this.conflicts.has(meta.n.id));
3363
- el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror);
3397
+ el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror && !meta.n.item.preview);
3364
3398
  }
3365
3399
  selectedSet() {
3366
3400
  const s = this.opts.selected;
@@ -3370,7 +3404,7 @@ var Calendar = class {
3370
3404
  this.selectedIds = this.selectedSet();
3371
3405
  for (const el of this.viewHost.querySelectorAll(".tnc-item")) {
3372
3406
  const meta = this.meta.get(el);
3373
- if (meta) el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror);
3407
+ if (meta) el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror && !meta.n.item.preview);
3374
3408
  }
3375
3409
  }
3376
3410
  destroyItemEl(el) {
@@ -3690,14 +3724,26 @@ var Calendar = class {
3690
3724
  if (e.icon) b.replaceChildren(e.icon());
3691
3725
  return b;
3692
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"));
3693
3739
  const nav = h(
3694
3740
  this.doc,
3695
3741
  "div",
3696
3742
  "tnc-toolbar-group tnc-toolbar-nav",
3697
3743
  null,
3698
- btn("\u2039", this.text.prev, "tnc-btn-prev", () => this.prev()),
3744
+ prev,
3699
3745
  btn(this.text.today, this.text.today, "tnc-btn-today", () => this.goToday()),
3700
- btn("\u203A", this.text.next, "tnc-btn-next", () => this.next())
3746
+ next
3701
3747
  );
3702
3748
  const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
3703
3749
  const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
@@ -3765,7 +3811,7 @@ var Calendar = class {
3765
3811
  day: meta.day,
3766
3812
  isStart: meta.isStart,
3767
3813
  isEnd: meta.isEnd,
3768
- isMirror: meta.mirror,
3814
+ isMirror: meta.mirror || meta.n.item.preview === true,
3769
3815
  variant: meta.variant,
3770
3816
  timeText: meta.n.allDay ? "" : end ? `${start} \u2013 ${end}` : start
3771
3817
  };
@@ -3910,7 +3956,7 @@ var Calendar = class {
3910
3956
  }
3911
3957
  itemElFrom(target) {
3912
3958
  const el = target?.closest?.(".tnc-item");
3913
- return el && this.root.contains(el) && this.meta.has(el) ? el : null;
3959
+ return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
3914
3960
  }
3915
3961
  onDocPointerDown = (ev) => {
3916
3962
  const t = ev.target;
@@ -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];
@@ -1118,17 +1135,22 @@ var DayGridView = class {
1118
1135
  const n = w.days.length;
1119
1136
  const hidden = new Array(n).fill([]).map(() => []);
1120
1137
  w.pool.begin();
1138
+ const previewLevels = new Array(n).fill(0);
1121
1139
  let maxLevel = -1;
1122
1140
  for (const p of row.placements) {
1123
1141
  let level = p.level;
1124
1142
  let room = Infinity;
1125
1143
  for (let c = p.startCol; c < p.endCol; c++) room = Math.min(room, visible(c));
1126
1144
  if (level >= room) {
1127
- if (p.item.id !== ctx.draggingId) {
1145
+ if (!p.item.item.preview && p.item.id !== ctx.draggingId) {
1128
1146
  for (let c = p.startCol; c < p.endCol; c++) hidden[c].push(p.item);
1129
1147
  continue;
1130
1148
  }
1131
- level = Math.max(0, room - 1);
1149
+ if (p.item.item.preview) {
1150
+ level = 0;
1151
+ for (let c = p.startCol; c < p.endCol; c++) level = Math.max(level, visible(c) + 1, previewLevels[c]);
1152
+ for (let c = p.startCol; c < p.endCol; c++) previewLevels[c] = level + 1;
1153
+ } else level = Math.max(0, room - 1);
1132
1154
  }
1133
1155
  maxLevel = Math.max(maxLevel, level);
1134
1156
  const single = !p.item.allDay && p.item.endDay - p.item.startDay <= 1;
@@ -1697,6 +1719,11 @@ var DateStrip = class {
1697
1719
 
1698
1720
  // src/dom/views/timegrid.ts
1699
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
+ }
1700
1727
  var TimeGridView = class {
1701
1728
  constructor(ctx) {
1702
1729
  this.ctx = ctx;
@@ -1913,17 +1940,22 @@ var TimeGridView = class {
1913
1940
  const pool = this.allDayPool;
1914
1941
  pool.begin();
1915
1942
  const hidden = new Array(days.length).fill(0);
1943
+ const previewLevels = new Array(days.length).fill(0);
1916
1944
  let maxLevel = -1;
1917
1945
  for (const p of row.placements) {
1918
1946
  let level = p.level;
1919
1947
  let room = Infinity;
1920
1948
  for (let c = p.startCol; c < p.endCol; c++) room = Math.min(room, visibleLevels(c));
1921
1949
  if (level >= room) {
1922
- if (p.item.id !== ctx.draggingId) {
1950
+ if (!p.item.item.preview && p.item.id !== ctx.draggingId) {
1923
1951
  for (let c = p.startCol; c < p.endCol; c++) hidden[c]++;
1924
1952
  continue;
1925
1953
  }
1926
- level = Math.max(0, room - 1);
1954
+ if (p.item.item.preview) {
1955
+ level = 0;
1956
+ for (let c = p.startCol; c < p.endCol; c++) level = Math.max(level, visibleLevels(c) + 1, previewLevels[c]);
1957
+ for (let c = p.startCol; c < p.endCol; c++) previewLevels[c] = level + 1;
1958
+ } else level = Math.max(0, room - 1);
1927
1959
  }
1928
1960
  maxLevel = Math.max(maxLevel, level);
1929
1961
  const el = pool.place(p.item.id, {
@@ -2001,18 +2033,20 @@ var TimeGridView = class {
2001
2033
  c.bg.end();
2002
2034
  const hasActual = layout.actual.length > 0;
2003
2035
  c.col.classList.toggle("has-actual", hasActual);
2004
- this.placeLane(c.plan, layout.plan, c, top, height, true);
2005
- 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");
2006
2038
  }
2007
- placeLane(pool, placements, c, top, height, withMore) {
2039
+ placeLane(pool, placements, c, top, height, lane) {
2008
2040
  const { ctx } = this;
2009
2041
  const { opts } = ctx;
2042
+ const withMore = lane === "plan";
2010
2043
  const maxStack = opts.eventMaxStack;
2011
2044
  const hiddenByCluster = /* @__PURE__ */ new Map();
2012
2045
  pool.begin();
2013
2046
  for (const p of placements) {
2014
- if (withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
2015
- const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, items: [] };
2047
+ const preview = p.item.item.preview || p.item.id === ctx.draggingId;
2048
+ if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
2049
+ const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
2016
2050
  rec.count++;
2017
2051
  rec.start = Math.min(rec.start, p.startMin);
2018
2052
  rec.end = Math.max(rec.end, p.endMin);
@@ -2020,7 +2054,7 @@ var TimeGridView = class {
2020
2054
  hiddenByCluster.set(p.cluster, rec);
2021
2055
  continue;
2022
2056
  }
2023
- const cols = withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
2057
+ const cols = !preview && withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
2024
2058
  const span = Math.min(p.span, cols - p.col);
2025
2059
  const left = p.col / cols;
2026
2060
  let width = span / cols;
@@ -2031,8 +2065,7 @@ var TimeGridView = class {
2031
2065
  setStyle(el, {
2032
2066
  top: top(p.startMin),
2033
2067
  height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
2034
- left: pct(left),
2035
- width: `calc(${pct(width)} - 1px)`,
2068
+ ...inLane(lane, p.split, left, width),
2036
2069
  "z-index": String(p.col + 1)
2037
2070
  });
2038
2071
  }
@@ -2044,8 +2077,7 @@ var TimeGridView = class {
2044
2077
  const cols = maxStack;
2045
2078
  setStyle(link, {
2046
2079
  top: top(rec.start),
2047
- left: pct((cols - 1) / cols),
2048
- width: `calc(${pct(1 / cols)} - 1px)`
2080
+ ...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
2049
2081
  });
2050
2082
  link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
2051
2083
  c.more.append(link);
@@ -2831,6 +2863,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
2831
2863
 
2832
2864
  // src/dom/calendar.ts
2833
2865
  var EXTERNAL_ID = "__tnc_external__";
2866
+ var SVG_NS = "http://www.w3.org/2000/svg";
2834
2867
  var MAX_AGENDA_DAYS = 366;
2835
2868
  function sameOption(a, b, depth = 0) {
2836
2869
  if (a === b) return true;
@@ -3191,7 +3224,7 @@ var Calendar = class {
3191
3224
  return out;
3192
3225
  }
3193
3226
  isEditable(n, what) {
3194
- if (n.id === EXTERNAL_ID || this.opts.view === "year") return false;
3227
+ if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
3195
3228
  if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
3196
3229
  const it = n.item;
3197
3230
  const base = it.editable ?? this.opts.editable;
@@ -3217,6 +3250,7 @@ var Calendar = class {
3217
3250
  createItemEl(meta) {
3218
3251
  const { n, variant } = meta;
3219
3252
  const it = n.item;
3253
+ const preview = meta.mirror || it.preview === true;
3220
3254
  const el = h(this.doc, "div", "tnc-item");
3221
3255
  el.classList.add(`tnc-item--${variant}`);
3222
3256
  if (it.kind) el.classList.add(`tnc-k-${it.kind.replace(/[^\w-]/g, "-")}`);
@@ -3226,12 +3260,12 @@ var Calendar = class {
3226
3260
  if (!meta.isStart) el.classList.add("is-cont-start");
3227
3261
  if (!meta.isEnd) el.classList.add("is-cont-end");
3228
3262
  if (n.allDay) el.classList.add("is-all-day");
3229
- if (meta.mirror || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
3263
+ if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
3230
3264
  if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
3231
3265
  const indicator = variant === "indicator";
3232
- if (!meta.mirror && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
3266
+ if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
3233
3267
  if (this.draggingId === n.id) el.classList.add("is-dragging");
3234
- if (this.selectedIds.has(n.id) && !meta.mirror) el.classList.add("is-selected");
3268
+ if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
3235
3269
  el.classList.add(...classList(it.className));
3236
3270
  const arg = this.itemArg(el, meta);
3237
3271
  el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
@@ -3242,7 +3276,7 @@ var Calendar = class {
3242
3276
  if (it.backgroundColor) el.style.setProperty("--tnc-bg", it.backgroundColor);
3243
3277
  if (it.borderColor) el.style.setProperty("--tnc-border", it.borderColor);
3244
3278
  if (it.textColor) el.style.setProperty("--tnc-fg", it.textColor);
3245
- if (indicator) el.setAttribute("aria-hidden", "true");
3279
+ if (indicator || preview) el.setAttribute("aria-hidden", "true");
3246
3280
  else if (it.display !== "background") {
3247
3281
  el.tabIndex = 0;
3248
3282
  el.setAttribute("role", "button");
@@ -3259,7 +3293,7 @@ var Calendar = class {
3259
3293
  } else {
3260
3294
  el.append(this.defaultContent(meta));
3261
3295
  }
3262
- if (variant !== "list" && !indicator && it.display !== "background") this.addHandles(el, meta);
3296
+ if (!preview && variant !== "list" && !indicator && it.display !== "background") this.addHandles(el, meta);
3263
3297
  this.opts.itemDidMount?.(arg);
3264
3298
  return el;
3265
3299
  }
@@ -3268,7 +3302,7 @@ var Calendar = class {
3268
3302
  if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
3269
3303
  el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
3270
3304
  el.classList.toggle("is-conflict", this.conflicts.has(meta.n.id));
3271
- el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror);
3305
+ el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror && !meta.n.item.preview);
3272
3306
  }
3273
3307
  selectedSet() {
3274
3308
  const s = this.opts.selected;
@@ -3278,7 +3312,7 @@ var Calendar = class {
3278
3312
  this.selectedIds = this.selectedSet();
3279
3313
  for (const el of this.viewHost.querySelectorAll(".tnc-item")) {
3280
3314
  const meta = this.meta.get(el);
3281
- if (meta) el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror);
3315
+ if (meta) el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror && !meta.n.item.preview);
3282
3316
  }
3283
3317
  }
3284
3318
  destroyItemEl(el) {
@@ -3598,14 +3632,26 @@ var Calendar = class {
3598
3632
  if (e.icon) b.replaceChildren(e.icon());
3599
3633
  return b;
3600
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"));
3601
3647
  const nav = h(
3602
3648
  this.doc,
3603
3649
  "div",
3604
3650
  "tnc-toolbar-group tnc-toolbar-nav",
3605
3651
  null,
3606
- btn("\u2039", this.text.prev, "tnc-btn-prev", () => this.prev()),
3652
+ prev,
3607
3653
  btn(this.text.today, this.text.today, "tnc-btn-today", () => this.goToday()),
3608
- btn("\u203A", this.text.next, "tnc-btn-next", () => this.next())
3654
+ next
3609
3655
  );
3610
3656
  const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
3611
3657
  const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
@@ -3673,7 +3719,7 @@ var Calendar = class {
3673
3719
  day: meta.day,
3674
3720
  isStart: meta.isStart,
3675
3721
  isEnd: meta.isEnd,
3676
- isMirror: meta.mirror,
3722
+ isMirror: meta.mirror || meta.n.item.preview === true,
3677
3723
  variant: meta.variant,
3678
3724
  timeText: meta.n.allDay ? "" : end ? `${start} \u2013 ${end}` : start
3679
3725
  };
@@ -3818,7 +3864,7 @@ var Calendar = class {
3818
3864
  }
3819
3865
  itemElFrom(target) {
3820
3866
  const el = target?.closest?.(".tnc-item");
3821
- return el && this.root.contains(el) && this.meta.has(el) ? el : null;
3867
+ return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
3822
3868
  }
3823
3869
  onDocPointerDown = (ev) => {
3824
3870
  const t = ev.target;
@@ -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 {
@@ -512,17 +512,22 @@ var DayGridView = class {
512
512
  const n = w.days.length;
513
513
  const hidden = new Array(n).fill([]).map(() => []);
514
514
  w.pool.begin();
515
+ const previewLevels = new Array(n).fill(0);
515
516
  let maxLevel = -1;
516
517
  for (const p of row.placements) {
517
518
  let level = p.level;
518
519
  let room = Infinity;
519
520
  for (let c = p.startCol; c < p.endCol; c++) room = Math.min(room, visible(c));
520
521
  if (level >= room) {
521
- if (p.item.id !== ctx.draggingId) {
522
+ if (!p.item.item.preview && p.item.id !== ctx.draggingId) {
522
523
  for (let c = p.startCol; c < p.endCol; c++) hidden[c].push(p.item);
523
524
  continue;
524
525
  }
525
- level = Math.max(0, room - 1);
526
+ if (p.item.item.preview) {
527
+ level = 0;
528
+ for (let c = p.startCol; c < p.endCol; c++) level = Math.max(level, visible(c) + 1, previewLevels[c]);
529
+ for (let c = p.startCol; c < p.endCol; c++) previewLevels[c] = level + 1;
530
+ } else level = Math.max(0, room - 1);
526
531
  }
527
532
  maxLevel = Math.max(maxLevel, level);
528
533
  const single = !p.item.allDay && p.item.endDay - p.item.startDay <= 1;
@@ -1091,6 +1096,11 @@ var DateStrip = class {
1091
1096
 
1092
1097
  // src/dom/views/timegrid.ts
1093
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
+ }
1094
1104
  var TimeGridView = class {
1095
1105
  constructor(ctx) {
1096
1106
  this.ctx = ctx;
@@ -1307,17 +1317,22 @@ var TimeGridView = class {
1307
1317
  const pool = this.allDayPool;
1308
1318
  pool.begin();
1309
1319
  const hidden = new Array(days.length).fill(0);
1320
+ const previewLevels = new Array(days.length).fill(0);
1310
1321
  let maxLevel = -1;
1311
1322
  for (const p of row.placements) {
1312
1323
  let level = p.level;
1313
1324
  let room = Infinity;
1314
1325
  for (let c = p.startCol; c < p.endCol; c++) room = Math.min(room, visibleLevels(c));
1315
1326
  if (level >= room) {
1316
- if (p.item.id !== ctx.draggingId) {
1327
+ if (!p.item.item.preview && p.item.id !== ctx.draggingId) {
1317
1328
  for (let c = p.startCol; c < p.endCol; c++) hidden[c]++;
1318
1329
  continue;
1319
1330
  }
1320
- level = Math.max(0, room - 1);
1331
+ if (p.item.item.preview) {
1332
+ level = 0;
1333
+ for (let c = p.startCol; c < p.endCol; c++) level = Math.max(level, visibleLevels(c) + 1, previewLevels[c]);
1334
+ for (let c = p.startCol; c < p.endCol; c++) previewLevels[c] = level + 1;
1335
+ } else level = Math.max(0, room - 1);
1321
1336
  }
1322
1337
  maxLevel = Math.max(maxLevel, level);
1323
1338
  const el = pool.place(p.item.id, {
@@ -1395,18 +1410,20 @@ var TimeGridView = class {
1395
1410
  c.bg.end();
1396
1411
  const hasActual = layout.actual.length > 0;
1397
1412
  c.col.classList.toggle("has-actual", hasActual);
1398
- this.placeLane(c.plan, layout.plan, c, top, height, true);
1399
- 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");
1400
1415
  }
1401
- placeLane(pool, placements, c, top, height, withMore) {
1416
+ placeLane(pool, placements, c, top, height, lane) {
1402
1417
  const { ctx } = this;
1403
1418
  const { opts } = ctx;
1419
+ const withMore = lane === "plan";
1404
1420
  const maxStack = opts.eventMaxStack;
1405
1421
  const hiddenByCluster = /* @__PURE__ */ new Map();
1406
1422
  pool.begin();
1407
1423
  for (const p of placements) {
1408
- if (withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
1409
- const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, items: [] };
1424
+ const preview = p.item.item.preview || p.item.id === ctx.draggingId;
1425
+ if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
1426
+ const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
1410
1427
  rec.count++;
1411
1428
  rec.start = Math.min(rec.start, p.startMin);
1412
1429
  rec.end = Math.max(rec.end, p.endMin);
@@ -1414,7 +1431,7 @@ var TimeGridView = class {
1414
1431
  hiddenByCluster.set(p.cluster, rec);
1415
1432
  continue;
1416
1433
  }
1417
- const cols = withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
1434
+ const cols = !preview && withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
1418
1435
  const span = Math.min(p.span, cols - p.col);
1419
1436
  const left = p.col / cols;
1420
1437
  let width = span / cols;
@@ -1425,8 +1442,7 @@ var TimeGridView = class {
1425
1442
  setStyle(el, {
1426
1443
  top: top(p.startMin),
1427
1444
  height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
1428
- left: pct(left),
1429
- width: `calc(${pct(width)} - 1px)`,
1445
+ ...inLane(lane, p.split, left, width),
1430
1446
  "z-index": String(p.col + 1)
1431
1447
  });
1432
1448
  }
@@ -1438,8 +1454,7 @@ var TimeGridView = class {
1438
1454
  const cols = maxStack;
1439
1455
  setStyle(link, {
1440
1456
  top: top(rec.start),
1441
- left: pct((cols - 1) / cols),
1442
- width: `calc(${pct(1 / cols)} - 1px)`
1457
+ ...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
1443
1458
  });
1444
1459
  link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
1445
1460
  c.more.append(link);
@@ -2225,6 +2240,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
2225
2240
 
2226
2241
  // src/dom/calendar.ts
2227
2242
  var EXTERNAL_ID = "__tnc_external__";
2243
+ var SVG_NS = "http://www.w3.org/2000/svg";
2228
2244
  var MAX_AGENDA_DAYS = 366;
2229
2245
  function sameOption(a, b, depth = 0) {
2230
2246
  if (a === b) return true;
@@ -2585,7 +2601,7 @@ var Calendar = class {
2585
2601
  return out;
2586
2602
  }
2587
2603
  isEditable(n, what) {
2588
- if (n.id === EXTERNAL_ID || this.opts.view === "year") return false;
2604
+ if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
2589
2605
  if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
2590
2606
  const it = n.item;
2591
2607
  const base = it.editable ?? this.opts.editable;
@@ -2611,6 +2627,7 @@ var Calendar = class {
2611
2627
  createItemEl(meta) {
2612
2628
  const { n, variant } = meta;
2613
2629
  const it = n.item;
2630
+ const preview = meta.mirror || it.preview === true;
2614
2631
  const el = h(this.doc, "div", "tnc-item");
2615
2632
  el.classList.add(`tnc-item--${variant}`);
2616
2633
  if (it.kind) el.classList.add(`tnc-k-${it.kind.replace(/[^\w-]/g, "-")}`);
@@ -2620,12 +2637,12 @@ var Calendar = class {
2620
2637
  if (!meta.isStart) el.classList.add("is-cont-start");
2621
2638
  if (!meta.isEnd) el.classList.add("is-cont-end");
2622
2639
  if (n.allDay) el.classList.add("is-all-day");
2623
- if (meta.mirror || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
2640
+ if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
2624
2641
  if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
2625
2642
  const indicator = variant === "indicator";
2626
- if (!meta.mirror && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
2643
+ if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
2627
2644
  if (this.draggingId === n.id) el.classList.add("is-dragging");
2628
- if (this.selectedIds.has(n.id) && !meta.mirror) el.classList.add("is-selected");
2645
+ if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
2629
2646
  el.classList.add(...classList(it.className));
2630
2647
  const arg = this.itemArg(el, meta);
2631
2648
  el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
@@ -2636,7 +2653,7 @@ var Calendar = class {
2636
2653
  if (it.backgroundColor) el.style.setProperty("--tnc-bg", it.backgroundColor);
2637
2654
  if (it.borderColor) el.style.setProperty("--tnc-border", it.borderColor);
2638
2655
  if (it.textColor) el.style.setProperty("--tnc-fg", it.textColor);
2639
- if (indicator) el.setAttribute("aria-hidden", "true");
2656
+ if (indicator || preview) el.setAttribute("aria-hidden", "true");
2640
2657
  else if (it.display !== "background") {
2641
2658
  el.tabIndex = 0;
2642
2659
  el.setAttribute("role", "button");
@@ -2653,7 +2670,7 @@ var Calendar = class {
2653
2670
  } else {
2654
2671
  el.append(this.defaultContent(meta));
2655
2672
  }
2656
- if (variant !== "list" && !indicator && it.display !== "background") this.addHandles(el, meta);
2673
+ if (!preview && variant !== "list" && !indicator && it.display !== "background") this.addHandles(el, meta);
2657
2674
  this.opts.itemDidMount?.(arg);
2658
2675
  return el;
2659
2676
  }
@@ -2662,7 +2679,7 @@ var Calendar = class {
2662
2679
  if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
2663
2680
  el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
2664
2681
  el.classList.toggle("is-conflict", this.conflicts.has(meta.n.id));
2665
- el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror);
2682
+ el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror && !meta.n.item.preview);
2666
2683
  }
2667
2684
  selectedSet() {
2668
2685
  const s = this.opts.selected;
@@ -2672,7 +2689,7 @@ var Calendar = class {
2672
2689
  this.selectedIds = this.selectedSet();
2673
2690
  for (const el of this.viewHost.querySelectorAll(".tnc-item")) {
2674
2691
  const meta = this.meta.get(el);
2675
- if (meta) el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror);
2692
+ if (meta) el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror && !meta.n.item.preview);
2676
2693
  }
2677
2694
  }
2678
2695
  destroyItemEl(el) {
@@ -2992,14 +3009,26 @@ var Calendar = class {
2992
3009
  if (e.icon) b.replaceChildren(e.icon());
2993
3010
  return b;
2994
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"));
2995
3024
  const nav = h(
2996
3025
  this.doc,
2997
3026
  "div",
2998
3027
  "tnc-toolbar-group tnc-toolbar-nav",
2999
3028
  null,
3000
- btn("\u2039", this.text.prev, "tnc-btn-prev", () => this.prev()),
3029
+ prev,
3001
3030
  btn(this.text.today, this.text.today, "tnc-btn-today", () => this.goToday()),
3002
- btn("\u203A", this.text.next, "tnc-btn-next", () => this.next())
3031
+ next
3003
3032
  );
3004
3033
  const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
3005
3034
  const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
@@ -3067,7 +3096,7 @@ var Calendar = class {
3067
3096
  day: meta.day,
3068
3097
  isStart: meta.isStart,
3069
3098
  isEnd: meta.isEnd,
3070
- isMirror: meta.mirror,
3099
+ isMirror: meta.mirror || meta.n.item.preview === true,
3071
3100
  variant: meta.variant,
3072
3101
  timeText: meta.n.allDay ? "" : end ? `${start} \u2013 ${end}` : start
3073
3102
  };
@@ -3212,7 +3241,7 @@ var Calendar = class {
3212
3241
  }
3213
3242
  itemElFrom(target) {
3214
3243
  const el = target?.closest?.(".tnc-item");
3215
- return el && this.root.contains(el) && this.meta.has(el) ? el : null;
3244
+ return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
3216
3245
  }
3217
3246
  onDocPointerDown = (ev) => {
3218
3247
  const t = ev.target;
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-26DEUGQH.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-26DEUGQH.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;
@@ -26,6 +26,8 @@ export interface CalendarItem<D = unknown> {
26
26
  borderColor?: string;
27
27
  textColor?: string;
28
28
  className?: string | string[];
29
+ /** Host-managed temporary placement: inert, mirror-styled and never hidden by event limits. */
30
+ preview?: boolean;
29
31
  /** Overrides the calendar-wide `editable` option. */
30
32
  editable?: boolean;
31
33
  startEditable?: boolean;
@@ -26,6 +26,8 @@ export interface CalendarItem<D = unknown> {
26
26
  borderColor?: string;
27
27
  textColor?: string;
28
28
  className?: string | string[];
29
+ /** Host-managed temporary placement: inert, mirror-styled and never hidden by event limits. */
30
+ preview?: boolean;
29
31
  /** Overrides the calendar-wide `editable` option. */
30
32
  editable?: boolean;
31
33
  startEditable?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tasknotes/calendar",
3
- "version": "0.1.4",
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",