@tasknotes/calendar 0.1.8 → 0.1.11

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,6 +1,28 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.1.11
4
+
5
+ - Hour-long timed items keep their time in narrow columns. Only single-row short items drop the time label when narrow; in the stacked layout the title is clamped to the lines left above the time (callumalpass/tasknotes#2528).
6
+ - Back-to-back timed items no longer count as overlapping: an item starting exactly where a short item ends stays in the same column instead of going side by side or into a collision stack, and the short item is drawn at its real height (callumalpass/tasknotes#2529).
7
+
8
+ ## 0.1.10
9
+
10
+ - Freeze Month source layout during a move and draw an inert target placeholder; preserve timed drag width across collision layouts.
11
+ - Contain date-strip touch gestures, reveal the complete target month in Year view, and allow dense collision stacks independently of the overlap preference.
12
+ - Require a short pause at a drag edge before scrolling, so passing through the edge does not shift the grid. Cancellation still restores the original scroll position.
13
+ - Keep the whole requested hour label visible below the sticky day and all-day headers when scrolling to a time.
14
+ - Agenda strip paging moves the list to the focused day; scrollbar and keyboard scrolling update the date highlight after a day is picked. Activity dots show up to three appointments in their configured colours.
15
+ - Hosts can supply one wall-clock `formatTime` callback for event, stack, selection, accessibility and slot labels.
16
+ - Hosts can set `overlapStackThreshold` to group dense overlapping appointments into the existing collision stack even in wide columns. Explicit side-by-side layouts remain unchanged.
17
+
18
+ ## 0.1.9
19
+
20
+ Not published to npm (the release check failed); these changes ship in 0.1.10.
21
+
22
+ ### Added
23
+
24
+ - Pinching a time grid on touch rescales its slots around the time under the fingers. `onSlotHeightChange` reports the settled height for hosts to store, and `pinchZoom: false` turns the gesture off.
25
+ - `basesLayoutOptions` reads `slotHeight` and `pinchZoom` from Bases calendar settings.
4
26
 
5
27
  ## 0.1.8
6
28
 
package/README.md CHANGED
@@ -46,6 +46,7 @@ The host persists changes; the calendar supplies optimistic updates and a
46
46
  - **Changes** (`onItemDrop`, `onItemResize`, `onSelect`, `onExternalDrop`) report TaskNotes storage strings (`startStr`/`endStr`), `revert()`, and `source: "pointer" | "keyboard"`. Returning a rejected promise reverts.
47
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.
48
48
  - **Input**: mouse drag after 4px, touch long press (`longPressDelay`), keyboard (Enter/Space, Shift+F10, Alt+Arrow to move, Alt+Shift+Arrow to resize), external drags via `beginExternalDrag` or native HTML5 drops.
49
+ - **Pinch**: two fingers on a time grid rescale `slotHeight` between 16 and 96px, keeping the time under the fingers in place. `onSlotHeightChange` reports the settled height for the host to store; `pinchZoom: false` leaves the gesture to the browser.
49
50
  - **Popouts**: all listeners, timers and observers use the host element's `ownerDocument`; bare `window`/`document` are lint errors in `src`.
50
51
 
51
52
  ### Views and options
package/dist/calendar.css CHANGED
@@ -293,8 +293,42 @@
293
293
  .tnc-item--timed.is-short .tnc-item-time {
294
294
  flex: none;
295
295
  }
296
- @container (max-height: 50px) and (max-width: 239px) {
297
- .tnc-item--timed .tnc-item-time {
296
+ /* Stacked layout: the time keeps its line and the title gets the lines left over
297
+ (11px text: n title lines need 13.75 * (n + 1) + 1px). Single-row short items drop the time when narrow. */
298
+ .tnc-item--timed:not(.is-short) .tnc-item-title {
299
+ flex: 0 1 auto;
300
+ min-height: 0;
301
+ }
302
+ .tnc-item--timed:not(.is-short) .tnc-item-time {
303
+ flex: none;
304
+ }
305
+ @container (max-height: 97.2px) {
306
+ .tnc-item--timed:not(.is-short) .tnc-item-title {
307
+ -webkit-line-clamp: 5;
308
+ }
309
+ }
310
+ @container (max-height: 83.4px) {
311
+ .tnc-item--timed:not(.is-short) .tnc-item-title {
312
+ -webkit-line-clamp: 4;
313
+ }
314
+ }
315
+ @container (max-height: 69.7px) {
316
+ .tnc-item--timed:not(.is-short) .tnc-item-title {
317
+ -webkit-line-clamp: 3;
318
+ }
319
+ }
320
+ @container (max-height: 55.9px) {
321
+ .tnc-item--timed:not(.is-short) .tnc-item-title {
322
+ -webkit-line-clamp: 2;
323
+ }
324
+ }
325
+ @container (max-height: 42.2px) {
326
+ .tnc-item--timed:not(.is-short) .tnc-item-title {
327
+ -webkit-line-clamp: 1;
328
+ }
329
+ }
330
+ @container (max-width: 239px) {
331
+ .tnc-item--timed.is-short .tnc-item-time {
298
332
  display: none;
299
333
  }
300
334
  }
@@ -707,6 +741,10 @@
707
741
  .tnc-tg-body {
708
742
  position: relative;
709
743
  }
744
+ .tnc-tg--pinch .tnc-tg-body {
745
+ /* Pinches rescale the grid rather than zoom the page. */
746
+ touch-action: pan-x pan-y;
747
+ }
710
748
  .tnc-gutter {
711
749
  border-right: 1px solid var(--tnc-line);
712
750
  }
@@ -1196,6 +1234,12 @@
1196
1234
  font-weight: 650;
1197
1235
  line-height: 1;
1198
1236
  }
1237
+ .tnc-strip-dots {
1238
+ display: flex;
1239
+ justify-content: center;
1240
+ gap: 2px;
1241
+ min-height: 5px;
1242
+ }
1199
1243
  .tnc-strip-dot {
1200
1244
  width: 5px;
1201
1245
  height: 5px;
@@ -1906,6 +1950,22 @@
1906
1950
  outline: 1px solid var(--tnc-focus);
1907
1951
  outline-offset: -1px;
1908
1952
  }
1953
+ .tnc .tnc-item--timed.is-dragging {
1954
+ min-width: var(--tnc-drag-width);
1955
+ max-width: var(--tnc-drag-width);
1956
+ }
1957
+ .tnc .tnc-dg .tnc-item.is-dragging:not(.is-mirror) {
1958
+ opacity: var(--tnc-drag-opacity);
1959
+ box-shadow: none;
1960
+ outline: none;
1961
+ }
1962
+ .tnc .tnc-month-preview {
1963
+ position: absolute;
1964
+ pointer-events: none;
1965
+ outline: 2px dashed var(--tnc-accent);
1966
+ outline-offset: -2px;
1967
+ z-index: 10;
1968
+ }
1909
1969
  .tnc .tnc-item--list.is-dragging:not(.tnc-drag-preview) {
1910
1970
  opacity: var(--tnc-drag-opacity);
1911
1971
  box-shadow: none;
package/dist/cjs/core.cjs CHANGED
@@ -490,43 +490,44 @@ function segmentInWindow(n, day, minMinute, maxMinute, zone) {
490
490
  };
491
491
  }
492
492
  var defaultOrder = (a, b) => a.startMs - b.startMs || b.endMs - b.startMs - (a.endMs - a.startMs) || Number(b.allDay) - Number(a.allDay) || a.item.title.localeCompare(b.item.title);
493
+ function collides(a, b, minMinutes) {
494
+ if (b.startMin < a.endMin) return true;
495
+ if (b.startMin === a.endMin && a.endMin > a.startMin) return false;
496
+ return b.startMin < a.startMin + minMinutes;
497
+ }
498
+ function overlaps(a, b, minMinutes) {
499
+ return a.startMin <= b.startMin ? collides(a, b, minMinutes) : collides(b, a, minMinutes);
500
+ }
493
501
  function packTimed(segs, minMinutes, order = defaultOrder) {
494
- const sorted = segs.map((s) => ({ ...s, end: Math.max(s.endMin, s.startMin + minMinutes) })).sort((a, b) => a.startMin - b.startMin || b.end - a.end || order(a.item, b.item));
502
+ const sorted = [...segs].sort((a, b) => a.startMin - b.startMin || Math.max(b.endMin, b.startMin + minMinutes) - Math.max(a.endMin, a.startMin + minMinutes) || order(a.item, b.item));
495
503
  const out = [];
496
504
  let cluster = [];
497
- let colEnds = [];
498
- let clusterEnd = -Infinity;
505
+ let colLast = [];
499
506
  let clusterIndex = 0;
500
507
  const flush = () => {
501
508
  if (cluster.length) clusterIndex++;
502
- const cols = colEnds.length;
509
+ const cols = colLast.length;
503
510
  for (const p of cluster) {
504
511
  p.cols = cols;
505
512
  let span = 1;
506
- while (p.col + span < cols && !cluster.some((o) => o.col === p.col + span && o.startMin < p.end && p.startMin < o.end)) span++;
513
+ while (p.col + span < cols && !cluster.some((o) => o.col === p.col + span && overlaps(o, p, minMinutes))) span++;
507
514
  p.span = span;
508
515
  }
509
516
  cluster = [];
510
- colEnds = [];
517
+ colLast = [];
511
518
  };
512
519
  for (const s of sorted) {
513
- if (s.startMin >= clusterEnd) {
514
- flush();
515
- clusterEnd = -Infinity;
516
- }
517
- let col = colEnds.findIndex((end) => end <= s.startMin);
518
- if (col < 0) {
519
- col = colEnds.length;
520
- colEnds.push(0);
521
- }
522
- colEnds[col] = s.end;
520
+ if (!cluster.some((o) => collides(o, s, minMinutes))) flush();
521
+ let col = colLast.findIndex((last) => !collides(last, s, minMinutes));
522
+ if (col < 0) col = colLast.length;
523
523
  const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex, split: false };
524
+ colLast[col] = p;
524
525
  cluster.push(p);
525
526
  out.push(p);
526
- clusterEnd = Math.max(clusterEnd, s.end);
527
527
  }
528
528
  flush();
529
- for (const p of out) delete p.end;
529
+ const starts = new Set(out.map((p) => p.startMin));
530
+ for (const p of out) if (p.endMin > p.startMin && p.startMin + minMinutes > p.endMin && starts.has(p.endMin)) p.abutted = true;
530
531
  return out;
531
532
  }
532
533
  function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTES_PER_DAY]) {
@@ -548,15 +549,8 @@ function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTE
548
549
  }
549
550
  function markSplit(lane, other, minMinutes) {
550
551
  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
552
  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);
553
+ for (const p of lane) if (!split.has(p.cluster) && other.some((o) => overlaps(p, o, minMinutes))) split.add(p.cluster);
560
554
  for (const p of lane) p.split = split.has(p.cluster);
561
555
  }
562
556
  function layoutRow(items, days, order = defaultOrder) {