@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 +23 -1
- package/README.md +1 -0
- package/dist/calendar.css +62 -2
- package/dist/cjs/core.cjs +20 -26
- package/dist/cjs/index.cjs +225 -53
- package/dist/cjs/react.cjs +221 -53
- package/dist/esm/{chunk-66A6EMCU.js → chunk-JWORVABV.js} +20 -26
- package/dist/esm/{chunk-EXKFHPW2.js → chunk-TTM45UAN.js} +202 -28
- package/dist/esm/core.js +1 -1
- package/dist/esm/index.js +6 -2
- package/dist/esm/react.js +2 -2
- package/dist/types/core/layout.d.cts +5 -0
- package/dist/types/core/layout.d.ts +5 -0
- package/dist/types/dom/calendar.d.cts +8 -0
- package/dist/types/dom/calendar.d.ts +8 -0
- package/dist/types/dom/format.d.cts +2 -1
- package/dist/types/dom/format.d.ts +2 -1
- package/dist/types/dom/options.d.cts +6 -0
- package/dist/types/dom/options.d.ts +6 -0
- package/dist/types/dom/views/daygrid.d.cts +2 -0
- package/dist/types/dom/views/daygrid.d.ts +2 -0
- package/dist/types/dom/views/list.d.cts +1 -0
- package/dist/types/dom/views/list.d.ts +1 -0
- package/dist/types/dom/views/strip.d.cts +3 -1
- package/dist/types/dom/views/strip.d.ts +3 -1
- package/dist/types/dom/views/timegrid.d.cts +4 -1
- package/dist/types/dom/views/timegrid.d.ts +4 -1
- package/dist/types/tasknotes/bases.d.cts +2 -0
- package/dist/types/tasknotes/bases.d.ts +2 -0
- package/package.json +1 -1
package/dist/cjs/react.cjs
CHANGED
|
@@ -575,14 +575,16 @@ function pct(n) {
|
|
|
575
575
|
|
|
576
576
|
// src/dom/format.ts
|
|
577
577
|
var Formatter = class {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
hour12;
|
|
581
|
-
constructor(locale, hour12) {
|
|
578
|
+
constructor(locale, hour12, formatTime) {
|
|
579
|
+
this.formatTime = formatTime;
|
|
582
580
|
this.locale = locale;
|
|
583
581
|
const probe = new Intl.DateTimeFormat(locale, { hour: "numeric" }).resolvedOptions();
|
|
584
582
|
this.hour12 = hour12 ?? (probe.hour12 === true || probe.hourCycle === "h12" || probe.hourCycle === "h11");
|
|
585
583
|
}
|
|
584
|
+
formatTime;
|
|
585
|
+
cache = /* @__PURE__ */ new Map();
|
|
586
|
+
locale;
|
|
587
|
+
hour12;
|
|
586
588
|
fmt(key, opts) {
|
|
587
589
|
let f = this.cache.get(key);
|
|
588
590
|
if (!f) {
|
|
@@ -640,6 +642,7 @@ var Formatter = class {
|
|
|
640
642
|
/** Wall-clock minutes to a label. `compact` drops ":00" in 12-hour formats ("9am"). */
|
|
641
643
|
time(minute, compact = false) {
|
|
642
644
|
const m = (Math.round(minute) % 1440 + 1440) % 1440;
|
|
645
|
+
if (this.formatTime) return this.formatTime(m);
|
|
643
646
|
const date = new Date(Date.UTC(2e3, 0, 1, Math.floor(m / 60), m % 60));
|
|
644
647
|
if (this.hour12) {
|
|
645
648
|
const parts = this.fmt("t12", { hour: "numeric", minute: "2-digit", hour12: true }).formatToParts(date);
|
|
@@ -731,6 +734,7 @@ function defaultOptions() {
|
|
|
731
734
|
snapDuration: void 0,
|
|
732
735
|
scrollTime: "08:00",
|
|
733
736
|
slotHeight: 24,
|
|
737
|
+
pinchZoom: true,
|
|
734
738
|
expandRows: false,
|
|
735
739
|
nowIndicator: true,
|
|
736
740
|
selectMirror: true,
|
|
@@ -772,6 +776,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
772
776
|
"locale",
|
|
773
777
|
"firstDay",
|
|
774
778
|
"hour12",
|
|
779
|
+
"formatTime",
|
|
775
780
|
"weekNumbers",
|
|
776
781
|
"weekends",
|
|
777
782
|
"hiddenDays",
|
|
@@ -784,9 +789,11 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
784
789
|
"slotDuration",
|
|
785
790
|
"snapDuration",
|
|
786
791
|
"slotHeight",
|
|
792
|
+
"pinchZoom",
|
|
787
793
|
"expandRows",
|
|
788
794
|
"nowIndicator",
|
|
789
795
|
"slotEventOverlap",
|
|
796
|
+
"overlapStackThreshold",
|
|
790
797
|
"eventMinHeight",
|
|
791
798
|
"eventMaxStack",
|
|
792
799
|
"dayMaxEvents",
|
|
@@ -831,43 +838,44 @@ function segmentInWindow(n, day, minMinute, maxMinute, zone) {
|
|
|
831
838
|
};
|
|
832
839
|
}
|
|
833
840
|
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);
|
|
841
|
+
function collides(a, b, minMinutes) {
|
|
842
|
+
if (b.startMin < a.endMin) return true;
|
|
843
|
+
if (b.startMin === a.endMin && a.endMin > a.startMin) return false;
|
|
844
|
+
return b.startMin < a.startMin + minMinutes;
|
|
845
|
+
}
|
|
846
|
+
function overlaps(a, b, minMinutes) {
|
|
847
|
+
return a.startMin <= b.startMin ? collides(a, b, minMinutes) : collides(b, a, minMinutes);
|
|
848
|
+
}
|
|
834
849
|
function packTimed(segs, minMinutes, order = defaultOrder) {
|
|
835
|
-
const sorted = segs.
|
|
850
|
+
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));
|
|
836
851
|
const out = [];
|
|
837
852
|
let cluster = [];
|
|
838
|
-
let
|
|
839
|
-
let clusterEnd = -Infinity;
|
|
853
|
+
let colLast = [];
|
|
840
854
|
let clusterIndex = 0;
|
|
841
855
|
const flush = () => {
|
|
842
856
|
if (cluster.length) clusterIndex++;
|
|
843
|
-
const cols =
|
|
857
|
+
const cols = colLast.length;
|
|
844
858
|
for (const p of cluster) {
|
|
845
859
|
p.cols = cols;
|
|
846
860
|
let span = 1;
|
|
847
|
-
while (p.col + span < cols && !cluster.some((o) => o.col === p.col + span && o
|
|
861
|
+
while (p.col + span < cols && !cluster.some((o) => o.col === p.col + span && overlaps(o, p, minMinutes))) span++;
|
|
848
862
|
p.span = span;
|
|
849
863
|
}
|
|
850
864
|
cluster = [];
|
|
851
|
-
|
|
865
|
+
colLast = [];
|
|
852
866
|
};
|
|
853
867
|
for (const s of sorted) {
|
|
854
|
-
if (
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
}
|
|
858
|
-
let col = colEnds.findIndex((end) => end <= s.startMin);
|
|
859
|
-
if (col < 0) {
|
|
860
|
-
col = colEnds.length;
|
|
861
|
-
colEnds.push(0);
|
|
862
|
-
}
|
|
863
|
-
colEnds[col] = s.end;
|
|
868
|
+
if (!cluster.some((o) => collides(o, s, minMinutes))) flush();
|
|
869
|
+
let col = colLast.findIndex((last) => !collides(last, s, minMinutes));
|
|
870
|
+
if (col < 0) col = colLast.length;
|
|
864
871
|
const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex, split: false };
|
|
872
|
+
colLast[col] = p;
|
|
865
873
|
cluster.push(p);
|
|
866
874
|
out.push(p);
|
|
867
|
-
clusterEnd = Math.max(clusterEnd, s.end);
|
|
868
875
|
}
|
|
869
876
|
flush();
|
|
870
|
-
|
|
877
|
+
const starts = new Set(out.map((p) => p.startMin));
|
|
878
|
+
for (const p of out) if (p.endMin > p.startMin && p.startMin + minMinutes > p.endMin && starts.has(p.endMin)) p.abutted = true;
|
|
871
879
|
return out;
|
|
872
880
|
}
|
|
873
881
|
function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTES_PER_DAY]) {
|
|
@@ -889,15 +897,8 @@ function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTE
|
|
|
889
897
|
}
|
|
890
898
|
function markSplit(lane, other, minMinutes) {
|
|
891
899
|
if (!lane.length || !other.length) return;
|
|
892
|
-
const end = (p) => Math.max(p.endMin, p.startMin + minMinutes);
|
|
893
|
-
const ranges = /* @__PURE__ */ new Map();
|
|
894
|
-
for (const p of lane) {
|
|
895
|
-
const r = ranges.get(p.cluster);
|
|
896
|
-
if (r) r[1] = Math.max(r[1], end(p));
|
|
897
|
-
else ranges.set(p.cluster, [p.startMin, end(p)]);
|
|
898
|
-
}
|
|
899
900
|
const split = /* @__PURE__ */ new Set();
|
|
900
|
-
for (const
|
|
901
|
+
for (const p of lane) if (!split.has(p.cluster) && other.some((o) => overlaps(p, o, minMinutes))) split.add(p.cluster);
|
|
901
902
|
for (const p of lane) p.split = split.has(p.cluster);
|
|
902
903
|
}
|
|
903
904
|
function layoutRow(items, days, order = defaultOrder) {
|
|
@@ -1066,6 +1067,7 @@ var DayGridView = class {
|
|
|
1066
1067
|
topPx = 24;
|
|
1067
1068
|
lastHeights = "";
|
|
1068
1069
|
dotsMode;
|
|
1070
|
+
dragPreviews = [];
|
|
1069
1071
|
daysPerWeek() {
|
|
1070
1072
|
const { days } = this.ctx;
|
|
1071
1073
|
const first = days[0];
|
|
@@ -1079,6 +1081,36 @@ var DayGridView = class {
|
|
|
1079
1081
|
}
|
|
1080
1082
|
render(_change) {
|
|
1081
1083
|
const { ctx } = this;
|
|
1084
|
+
this.clearDragPreviews();
|
|
1085
|
+
const dragged = ctx.draggingId ? ctx.store.get(ctx.draggingId) : void 0;
|
|
1086
|
+
const source = dragged ? this.elementsFor(dragged.id)[0] : void 0;
|
|
1087
|
+
if (dragged && source && this.el.closest(".tnc-dragging-move")) {
|
|
1088
|
+
const proposals = [dragged, ...[...ctx.store.all()].filter((item) => item.id !== dragged.id && item.item.preview)];
|
|
1089
|
+
for (const proposal of proposals) {
|
|
1090
|
+
const original = this.elementsFor(proposal.id)[0] ?? source;
|
|
1091
|
+
const bounds = original.getBoundingClientRect();
|
|
1092
|
+
const sourceWeek = original.closest(".tnc-dg-week");
|
|
1093
|
+
const offset = bounds.top - sourceWeek.getBoundingClientRect().top;
|
|
1094
|
+
for (const week of this.weeks) {
|
|
1095
|
+
const start = week.days.findIndex((day) => day >= proposal.startDay && day < proposal.endDay);
|
|
1096
|
+
if (start < 0) continue;
|
|
1097
|
+
let end = start + 1;
|
|
1098
|
+
while (!this.dotsMode && end < week.days.length && week.days[end] < proposal.endDay) end++;
|
|
1099
|
+
const preview = ctx.createItemEl({ n: proposal, day: week.days[start], isStart: proposal.startDay >= week.days[start], isEnd: proposal.endDay <= week.days[end - 1] + 1, variant: ctx.meta.get(original).variant, mirror: true });
|
|
1100
|
+
preview.classList.add("tnc-month-preview");
|
|
1101
|
+
preview.inert = true;
|
|
1102
|
+
setStyle(preview, {
|
|
1103
|
+
left: `calc(${pct(start / week.days.length)} + 2px)`,
|
|
1104
|
+
width: `calc(${pct((end - start) / week.days.length)} - 4px)`,
|
|
1105
|
+
top: `${Math.max(this.topPx, Math.min(offset, week.row.clientHeight - bounds.height - 4))}px`,
|
|
1106
|
+
height: `${bounds.height}px`
|
|
1107
|
+
});
|
|
1108
|
+
week.layer.append(preview);
|
|
1109
|
+
this.dragPreviews.push(preview);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1082
1114
|
this.dotsMode = ctx.opts.monthDisplay !== "full" || this.grid.clientWidth / this.daysPerWeek() < 72;
|
|
1083
1115
|
this.el.classList.toggle("tnc-dg--dots", this.dotsMode);
|
|
1084
1116
|
this.el.classList.toggle("tnc-dg--compact", this.dotsMode && ctx.opts.monthDisplay === "full");
|
|
@@ -1110,6 +1142,10 @@ var DayGridView = class {
|
|
|
1110
1142
|
}
|
|
1111
1143
|
}
|
|
1112
1144
|
}
|
|
1145
|
+
clearDragPreviews() {
|
|
1146
|
+
for (const preview of this.dragPreviews) this.ctx.destroyItemEl(preview);
|
|
1147
|
+
this.dragPreviews = [];
|
|
1148
|
+
}
|
|
1113
1149
|
/** Height of a "+N more" link as the host styles it (`--tnc-more-h`, or a taller min-height). */
|
|
1114
1150
|
measureMore(cs) {
|
|
1115
1151
|
const fallback = px(cs, "--tnc-more-h", this.barPx);
|
|
@@ -1301,6 +1337,7 @@ var DayGridView = class {
|
|
|
1301
1337
|
return this.weeks.flatMap((w) => [...w.pool.elementsFor(id), ...(w.dots ?? []).flatMap((c) => c.pool.elementsFor(id))]);
|
|
1302
1338
|
}
|
|
1303
1339
|
destroy() {
|
|
1340
|
+
this.clearDragPreviews();
|
|
1304
1341
|
this.resizeObs?.disconnect();
|
|
1305
1342
|
for (const w of this.weeks) {
|
|
1306
1343
|
w.pool.clear();
|
|
@@ -1353,6 +1390,7 @@ var ListView = class {
|
|
|
1353
1390
|
frame = null;
|
|
1354
1391
|
/** A picked day stays marked until the list is scrolled by hand. */
|
|
1355
1392
|
pinned = false;
|
|
1393
|
+
pinnedTop = 0;
|
|
1356
1394
|
itemsOn(day) {
|
|
1357
1395
|
const { store, opts } = this.ctx;
|
|
1358
1396
|
const out = store.spanningIn(day, day + 1, true);
|
|
@@ -1484,6 +1522,7 @@ var ListView = class {
|
|
|
1484
1522
|
this.scroller.scrollTop += section.getBoundingClientRect().top - this.scroller.getBoundingClientRect().top;
|
|
1485
1523
|
this.ctx.strip?.setFocus(day);
|
|
1486
1524
|
this.pinned = true;
|
|
1525
|
+
this.pinnedTop = this.scroller.scrollTop;
|
|
1487
1526
|
}
|
|
1488
1527
|
fold = () => {
|
|
1489
1528
|
this.pinned = false;
|
|
@@ -1491,6 +1530,7 @@ var ListView = class {
|
|
|
1491
1530
|
if (strip?.expanded) strip.setExpanded(false);
|
|
1492
1531
|
};
|
|
1493
1532
|
onScroll = () => {
|
|
1533
|
+
if (this.pinned && Math.abs(this.scroller.scrollTop - this.pinnedTop) > 1) this.pinned = false;
|
|
1494
1534
|
if (this.frame !== null) return;
|
|
1495
1535
|
this.frame = this.ctx.win.requestAnimationFrame(() => {
|
|
1496
1536
|
this.frame = null;
|
|
@@ -1501,7 +1541,7 @@ var ListView = class {
|
|
|
1501
1541
|
followTop() {
|
|
1502
1542
|
const strip = this.ctx.strip;
|
|
1503
1543
|
if (!strip || this.pinned) return;
|
|
1504
|
-
const edge = strip.el.getBoundingClientRect().bottom + 1;
|
|
1544
|
+
const edge = Math.max(strip.el.getBoundingClientRect().bottom, this.scroller.getBoundingClientRect().top) + 1;
|
|
1505
1545
|
for (const day of this.ctx.days) {
|
|
1506
1546
|
const section = this.sections.get(day);
|
|
1507
1547
|
if (section && section.getBoundingClientRect().bottom > edge) {
|
|
@@ -1549,10 +1589,11 @@ var ListView = class {
|
|
|
1549
1589
|
// src/dom/views/strip.ts
|
|
1550
1590
|
var SWIPE_PX = 40;
|
|
1551
1591
|
var DateStrip = class {
|
|
1552
|
-
constructor(ctx, pick, focus, onShown) {
|
|
1592
|
+
constructor(ctx, pick, focus, onShown, onPage) {
|
|
1553
1593
|
this.ctx = ctx;
|
|
1554
1594
|
this.pick = pick;
|
|
1555
1595
|
this.onShown = onShown;
|
|
1596
|
+
this.onPage = onPage;
|
|
1556
1597
|
const { doc, text } = ctx;
|
|
1557
1598
|
this.focus = focus;
|
|
1558
1599
|
this.shown = startOfWeek(focus, ctx.firstDay);
|
|
@@ -1576,6 +1617,10 @@ var DateStrip = class {
|
|
|
1576
1617
|
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
1577
1618
|
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
1578
1619
|
this.el.addEventListener("pointerup", this.onPointerUp);
|
|
1620
|
+
this.el.addEventListener("touchstart", this.ownTouch, { passive: true });
|
|
1621
|
+
this.el.addEventListener("touchmove", this.ownTouch, { passive: true });
|
|
1622
|
+
this.el.addEventListener("touchend", this.ownTouch, { passive: true });
|
|
1623
|
+
this.el.addEventListener("touchcancel", this.ownTouch, { passive: true });
|
|
1579
1624
|
this.el.addEventListener("pointercancel", this.onPointerCancel);
|
|
1580
1625
|
this.el.addEventListener("click", this.onClickCapture, true);
|
|
1581
1626
|
this.draw();
|
|
@@ -1584,6 +1629,7 @@ var DateStrip = class {
|
|
|
1584
1629
|
ctx;
|
|
1585
1630
|
pick;
|
|
1586
1631
|
onShown;
|
|
1632
|
+
onPage;
|
|
1587
1633
|
el;
|
|
1588
1634
|
title;
|
|
1589
1635
|
dows;
|
|
@@ -1642,6 +1688,7 @@ var DateStrip = class {
|
|
|
1642
1688
|
step(dir) {
|
|
1643
1689
|
this.shown = this.expanded ? addMonths(this.shown, dir) : this.shown + 7 * dir;
|
|
1644
1690
|
this.draw();
|
|
1691
|
+
this.onPage?.(this.expanded ? this.shown : this.focus + 7 * dir);
|
|
1645
1692
|
}
|
|
1646
1693
|
days() {
|
|
1647
1694
|
if (!this.expanded) return Array.from({ length: 7 }, (_, i) => this.shown + i);
|
|
@@ -1666,7 +1713,8 @@ var DateStrip = class {
|
|
|
1666
1713
|
const tabDay = days.includes(this.focus) ? this.focus : days[0];
|
|
1667
1714
|
const cells = days.map((d) => {
|
|
1668
1715
|
const arg = ctx.dayArg(d);
|
|
1669
|
-
const
|
|
1716
|
+
const items = store.inRange(d, d + 1).filter((n) => n.item.display !== "background" && n.item.display !== "none");
|
|
1717
|
+
const count = items.length;
|
|
1670
1718
|
const first = ymdOfDay(d).day === 1 && d !== days[0] && !this.expanded;
|
|
1671
1719
|
const label = fmt.longDate(d);
|
|
1672
1720
|
const cell = h(
|
|
@@ -1690,7 +1738,15 @@ var DateStrip = class {
|
|
|
1690
1738
|
}
|
|
1691
1739
|
);
|
|
1692
1740
|
if (first) cell.append(h(doc, "span", "tnc-strip-month", { "aria-hidden": "true" }, fmt.monthShort(d)));
|
|
1693
|
-
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d))
|
|
1741
|
+
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d)));
|
|
1742
|
+
const dots = h(doc, "span", "tnc-strip-dots", { "aria-hidden": "true" });
|
|
1743
|
+
for (const item of items.slice(0, 3)) {
|
|
1744
|
+
const dot = h(doc, "span", "tnc-strip-dot");
|
|
1745
|
+
const color = item.item.color || item.item.borderColor || item.item.backgroundColor;
|
|
1746
|
+
if (color) dot.style.backgroundColor = color;
|
|
1747
|
+
dots.append(dot);
|
|
1748
|
+
}
|
|
1749
|
+
cell.append(dots);
|
|
1694
1750
|
return cell;
|
|
1695
1751
|
});
|
|
1696
1752
|
this.grid.replaceChildren(...cells);
|
|
@@ -1730,6 +1786,9 @@ var DateStrip = class {
|
|
|
1730
1786
|
if (ev.button !== 0 || this.ctx.draggingId) return;
|
|
1731
1787
|
this.swipe = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
|
|
1732
1788
|
};
|
|
1789
|
+
ownTouch = (event) => {
|
|
1790
|
+
event.stopPropagation();
|
|
1791
|
+
};
|
|
1733
1792
|
onPointerUp = (ev) => {
|
|
1734
1793
|
const s = this.swipe;
|
|
1735
1794
|
this.swipe = null;
|
|
@@ -1769,6 +1828,7 @@ var TimeGridView = class {
|
|
|
1769
1828
|
const showGrid = opts.timeGrid;
|
|
1770
1829
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1771
1830
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1831
|
+
this.el.classList.toggle("tnc-tg--pinch", showGrid && opts.pinchZoom);
|
|
1772
1832
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1773
1833
|
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1774
1834
|
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
@@ -1918,6 +1978,11 @@ var TimeGridView = class {
|
|
|
1918
1978
|
}
|
|
1919
1979
|
if (this.nowGutter) gutter.append(this.nowGutter);
|
|
1920
1980
|
}
|
|
1981
|
+
/** Slot height as drawn, which `expandRows` can stretch past the option. */
|
|
1982
|
+
get drawnSlotHeight() {
|
|
1983
|
+
return this.ctx.slot.duration * this.ppm;
|
|
1984
|
+
}
|
|
1985
|
+
/** Applies `slotHeight`. Items are placed in percentages, so they follow without a render. */
|
|
1921
1986
|
applyScale() {
|
|
1922
1987
|
const { opts, slot } = this.ctx;
|
|
1923
1988
|
this.ppm = opts.slotHeight / slot.duration;
|
|
@@ -2098,15 +2163,17 @@ var TimeGridView = class {
|
|
|
2098
2163
|
const { opts } = ctx;
|
|
2099
2164
|
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
2100
2165
|
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
2101
|
-
const
|
|
2166
|
+
const overlapThreshold = opts.overlapStackThreshold ?? 0;
|
|
2167
|
+
const collapsedClusters = new Set(placements.filter((placement) => {
|
|
2102
2168
|
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2169
|
+
const denseOverlap = overlapThreshold > 1 && placement.cols >= overlapThreshold;
|
|
2170
|
+
return columns > 1 && (columns * readableWidth > laneWidth || denseOverlap);
|
|
2171
|
+
}).map((placement) => placement.cluster));
|
|
2107
2172
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
2108
2173
|
pool.begin();
|
|
2109
2174
|
for (const p of placements) {
|
|
2175
|
+
const withMore = lane === "plan" || collapsedClusters.has(p.cluster);
|
|
2176
|
+
const maxStack = collapsedClusters.has(p.cluster) ? 1 : opts.eventMaxStack;
|
|
2110
2177
|
const preview = p.item.item.preview || p.item.id === ctx.draggingId;
|
|
2111
2178
|
if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
|
|
2112
2179
|
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
|
|
@@ -2119,7 +2186,7 @@ var TimeGridView = class {
|
|
|
2119
2186
|
}
|
|
2120
2187
|
const cols = !preview && withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
|
|
2121
2188
|
const span = Math.min(p.span, cols - p.col);
|
|
2122
|
-
const left = p.col / cols;
|
|
2189
|
+
const left = p.item.id === ctx.draggingId ? 0 : p.col / cols;
|
|
2123
2190
|
let width = span / cols;
|
|
2124
2191
|
if (opts.slotEventOverlap && cols > 1 && p.col + span < cols) width = Math.min(1 - left, width * 1.7);
|
|
2125
2192
|
const el = pool.place(p.item.id, this.meta(p, false));
|
|
@@ -2127,15 +2194,16 @@ var TimeGridView = class {
|
|
|
2127
2194
|
el.classList.toggle("is-short", short);
|
|
2128
2195
|
setStyle(el, {
|
|
2129
2196
|
top: top(p.startMin),
|
|
2130
|
-
height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
2197
|
+
height: p.abutted ? height(p) : `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
2131
2198
|
...inLane(lane, p.split, left, width),
|
|
2132
2199
|
"--tnc-event-readable-width": pct(span / cols / width),
|
|
2133
2200
|
"z-index": String(p.col + 1)
|
|
2134
2201
|
});
|
|
2135
2202
|
}
|
|
2136
2203
|
pool.end();
|
|
2137
|
-
if (!withMore) return;
|
|
2138
2204
|
for (const [cluster, rec] of hiddenByCluster) {
|
|
2205
|
+
const collapse = collapsedClusters.has(cluster);
|
|
2206
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2139
2207
|
const key = `${lane}-${cluster}`;
|
|
2140
2208
|
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
2141
2209
|
if (!link) {
|
|
@@ -2301,7 +2369,9 @@ var TimeGridView = class {
|
|
|
2301
2369
|
scrollToTime(minute) {
|
|
2302
2370
|
if (!this.body) return;
|
|
2303
2371
|
const y = (minute - this.ctx.slot.min) * this.ppm;
|
|
2304
|
-
|
|
2372
|
+
const label = this.gutter?.querySelector(".tnc-slot-label");
|
|
2373
|
+
const inset = label ? Math.ceil(label.getBoundingClientRect().height / 2) + 2 : 0;
|
|
2374
|
+
this.scroller.scrollTop = Math.max(0, y - inset);
|
|
2305
2375
|
}
|
|
2306
2376
|
getScroll() {
|
|
2307
2377
|
return { top: this.scroller.scrollTop, left: this.scroller.scrollLeft };
|
|
@@ -2965,7 +3035,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2965
3035
|
const bounds = month.getBoundingClientRect();
|
|
2966
3036
|
const viewport = this.scroller.getBoundingClientRect();
|
|
2967
3037
|
if (bounds.top < viewport.top) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
2968
|
-
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.
|
|
3038
|
+
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
2969
3039
|
}
|
|
2970
3040
|
elementsFor(_id) {
|
|
2971
3041
|
return [];
|
|
@@ -2979,6 +3049,8 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2979
3049
|
var EXTERNAL_ID = "__tnc_external__";
|
|
2980
3050
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
2981
3051
|
var MAX_AGENDA_DAYS = 366;
|
|
3052
|
+
var PINCH_MIN_SLOT = 16;
|
|
3053
|
+
var PINCH_MAX_SLOT = 96;
|
|
2982
3054
|
function sameOption(a, b, depth = 0) {
|
|
2983
3055
|
if (a === b) return true;
|
|
2984
3056
|
if (typeof a === "function" && typeof b === "function") return true;
|
|
@@ -3006,7 +3078,7 @@ var Calendar = class {
|
|
|
3006
3078
|
this.win = this.doc.defaultView ?? globalThis;
|
|
3007
3079
|
this.opts = { ...defaultOptions(), ...options };
|
|
3008
3080
|
this.zone = getTimeZone(this.opts.timeZone);
|
|
3009
|
-
this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
3081
|
+
this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
3010
3082
|
this.text = this.resolveText();
|
|
3011
3083
|
this.order = parseOrder(this.opts.eventOrder);
|
|
3012
3084
|
this.store = new ItemStore(this.zone, {
|
|
@@ -3048,13 +3120,16 @@ var Calendar = class {
|
|
|
3048
3120
|
draggingId = null;
|
|
3049
3121
|
listDragPreview = null;
|
|
3050
3122
|
dragSources = [];
|
|
3123
|
+
dragWidth = null;
|
|
3051
3124
|
dragScrollFrame = null;
|
|
3052
3125
|
dragScrollOrigins = /* @__PURE__ */ new Map();
|
|
3126
|
+
dragScrollEdges = /* @__PURE__ */ new Map();
|
|
3053
3127
|
itemWriteStates = /* @__PURE__ */ new Map();
|
|
3054
3128
|
dragRow = null;
|
|
3055
3129
|
strip = null;
|
|
3056
3130
|
stripSize = null;
|
|
3057
3131
|
rebuilding = false;
|
|
3132
|
+
pinch = null;
|
|
3058
3133
|
toolbarEl;
|
|
3059
3134
|
viewHost;
|
|
3060
3135
|
current = null;
|
|
@@ -3163,7 +3238,7 @@ var Calendar = class {
|
|
|
3163
3238
|
defaultAllDayDays: this.opts.defaultAllDayDays
|
|
3164
3239
|
});
|
|
3165
3240
|
}
|
|
3166
|
-
if (has("locale") || has("hour12")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
3241
|
+
if (has("locale") || has("hour12") || has("formatTime")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
3167
3242
|
if (has("text") || has("locale")) this.text = this.resolveText();
|
|
3168
3243
|
if (has("eventOrder")) this.order = parseOrder(this.opts.eventOrder);
|
|
3169
3244
|
if (has("date")) this.anchor = this.toDay(this.opts.date) ?? this.anchor;
|
|
@@ -3402,7 +3477,10 @@ var Calendar = class {
|
|
|
3402
3477
|
if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
|
|
3403
3478
|
const indicator = variant === "indicator";
|
|
3404
3479
|
if (!preview && !indicator && (this.isEditable(n, "start", true) || this.isEditable(n, "duration", true))) el.classList.add("is-draggable");
|
|
3405
|
-
if (this.draggingId === n.id)
|
|
3480
|
+
if (this.draggingId === n.id) {
|
|
3481
|
+
el.classList.add("is-dragging");
|
|
3482
|
+
if (variant === "timed" && this.dragWidth !== null) el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
3483
|
+
}
|
|
3406
3484
|
if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
|
|
3407
3485
|
el.classList.add(...classList(it.className));
|
|
3408
3486
|
const arg = this.itemArg(el, meta);
|
|
@@ -3670,7 +3748,9 @@ var Calendar = class {
|
|
|
3670
3748
|
this.root.classList.add("tnc--has-strip");
|
|
3671
3749
|
const { startDay, endDay } = this.view.range;
|
|
3672
3750
|
if (!this.strip) {
|
|
3673
|
-
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown())
|
|
3751
|
+
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown(), (day) => {
|
|
3752
|
+
if (this.agendaList()) this.showListDay(day);
|
|
3753
|
+
});
|
|
3674
3754
|
this.strip = strip;
|
|
3675
3755
|
this.root.insertBefore(strip.el, this.viewHost);
|
|
3676
3756
|
const RO = this.win.ResizeObserver;
|
|
@@ -4034,7 +4114,10 @@ var Calendar = class {
|
|
|
4034
4114
|
r.addEventListener("dragover", this.onNativeDragOver);
|
|
4035
4115
|
r.addEventListener("dragleave", this.onNativeDragLeave);
|
|
4036
4116
|
r.addEventListener("drop", this.onNativeDrop);
|
|
4117
|
+
r.addEventListener("touchstart", this.onTouchStart, { passive: true });
|
|
4037
4118
|
r.addEventListener("touchmove", this.onTouchMove, { passive: false });
|
|
4119
|
+
r.addEventListener("touchend", this.onTouchEnd);
|
|
4120
|
+
r.addEventListener("touchcancel", this.onTouchEnd);
|
|
4038
4121
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
4039
4122
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
4040
4123
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -4053,7 +4136,10 @@ var Calendar = class {
|
|
|
4053
4136
|
r.removeEventListener("dragover", this.onNativeDragOver);
|
|
4054
4137
|
r.removeEventListener("dragleave", this.onNativeDragLeave);
|
|
4055
4138
|
r.removeEventListener("drop", this.onNativeDrop);
|
|
4139
|
+
r.removeEventListener("touchstart", this.onTouchStart);
|
|
4056
4140
|
r.removeEventListener("touchmove", this.onTouchMove);
|
|
4141
|
+
r.removeEventListener("touchend", this.onTouchEnd);
|
|
4142
|
+
r.removeEventListener("touchcancel", this.onTouchEnd);
|
|
4057
4143
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
4058
4144
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
4059
4145
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -4148,7 +4234,11 @@ var Calendar = class {
|
|
|
4148
4234
|
if (s.origin) {
|
|
4149
4235
|
this.draggingId = s.origin.id;
|
|
4150
4236
|
for (const el of this.elementsFor(s.origin.id)) {
|
|
4151
|
-
if (this.
|
|
4237
|
+
if (this.meta.get(el)?.variant === "timed" && s.mode === "move") {
|
|
4238
|
+
this.dragWidth = el.getBoundingClientRect().width;
|
|
4239
|
+
el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
4240
|
+
}
|
|
4241
|
+
if (this.opts.view !== "list" && !(this.current instanceof DayGridView) && s.mode === "move") {
|
|
4152
4242
|
const source = el.cloneNode(true);
|
|
4153
4243
|
source.classList.remove("is-selected");
|
|
4154
4244
|
source.classList.add("tnc-drag-source");
|
|
@@ -4201,24 +4291,96 @@ var Calendar = class {
|
|
|
4201
4291
|
const coordinate = axis === "x" ? pointer.clientX : pointer.clientY;
|
|
4202
4292
|
const lower = Math.max(0, axis === "x" ? viewport.left : viewport.top);
|
|
4203
4293
|
const upper = Math.min(axis === "x" ? this.win.innerWidth : this.win.innerHeight, axis === "x" ? viewport.right : viewport.bottom);
|
|
4204
|
-
if (coordinate < lower || coordinate > upper)
|
|
4294
|
+
if (coordinate < lower || coordinate > upper) {
|
|
4295
|
+
this.dragScrollEdges.delete(axis);
|
|
4296
|
+
continue;
|
|
4297
|
+
}
|
|
4205
4298
|
const delta = coordinate < lower + 32 ? -Math.min(12, (lower + 32 - coordinate) / 2) : coordinate > upper - 32 ? Math.min(12, (coordinate - upper + 32) / 2) : 0;
|
|
4206
|
-
if (!delta)
|
|
4299
|
+
if (!delta) {
|
|
4300
|
+
this.dragScrollEdges.delete(axis);
|
|
4301
|
+
continue;
|
|
4302
|
+
}
|
|
4303
|
+
const edge = this.dragScrollEdges.get(axis);
|
|
4304
|
+
const direction = Math.sign(delta);
|
|
4305
|
+
const now = this.win.performance.now();
|
|
4306
|
+
if (!edge || edge.scroller !== scroller || edge.direction !== direction) {
|
|
4307
|
+
this.dragScrollEdges.set(axis, { scroller, direction, since: now });
|
|
4308
|
+
continue;
|
|
4309
|
+
}
|
|
4310
|
+
if (now - edge.since < 180) continue;
|
|
4207
4311
|
if (!this.dragScrollOrigins.has(scroller)) this.dragScrollOrigins.set(scroller, { top: scroller.scrollTop, left: scroller.scrollLeft });
|
|
4208
4312
|
const before = axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
|
|
4209
4313
|
if (axis === "x") scroller.scrollLeft += delta;
|
|
4210
4314
|
else scroller.scrollTop += delta;
|
|
4211
4315
|
moved ||= before !== (axis === "x" ? scroller.scrollLeft : scroller.scrollTop);
|
|
4212
4316
|
}
|
|
4213
|
-
}
|
|
4317
|
+
} else this.dragScrollEdges.clear();
|
|
4214
4318
|
if (moved) this.track(session, pointer);
|
|
4215
4319
|
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4216
4320
|
};
|
|
4217
4321
|
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4218
4322
|
}
|
|
4323
|
+
onTouchStart = (ev) => {
|
|
4324
|
+
const first = ev.touches[0];
|
|
4325
|
+
const second = ev.touches[1];
|
|
4326
|
+
if (!first || !second) {
|
|
4327
|
+
this.endPinch();
|
|
4328
|
+
return;
|
|
4329
|
+
}
|
|
4330
|
+
const view = this.current;
|
|
4331
|
+
if (this.session?.active || !this.opts.pinchZoom || !(view instanceof TimeGridView)) return;
|
|
4332
|
+
const body = first.target.closest(".tnc-tg-body");
|
|
4333
|
+
if (!body?.contains(second.target)) return;
|
|
4334
|
+
this.endSession(null, true);
|
|
4335
|
+
if (this.pinch) return;
|
|
4336
|
+
const { top, height } = body.getBoundingClientRect();
|
|
4337
|
+
const y = (first.clientY + second.clientY) / 2;
|
|
4338
|
+
this.pinch = {
|
|
4339
|
+
body,
|
|
4340
|
+
distance: Math.max(1, Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY)),
|
|
4341
|
+
y,
|
|
4342
|
+
anchor: (y - top) / height,
|
|
4343
|
+
drawn: view.drawnSlotHeight,
|
|
4344
|
+
option: this.opts.slotHeight
|
|
4345
|
+
};
|
|
4346
|
+
};
|
|
4219
4347
|
onTouchMove = (ev) => {
|
|
4220
4348
|
if (this.session?.active) ev.preventDefault();
|
|
4349
|
+
const p = this.pinch;
|
|
4350
|
+
const first = ev.touches[0];
|
|
4351
|
+
const second = ev.touches[1];
|
|
4352
|
+
if (!p || !first || !second) return;
|
|
4353
|
+
if (!ev.cancelable) {
|
|
4354
|
+
this.endPinch();
|
|
4355
|
+
return;
|
|
4356
|
+
}
|
|
4357
|
+
ev.preventDefault();
|
|
4358
|
+
const height = p.drawn * Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY) / p.distance;
|
|
4359
|
+
p.y = (first.clientY + second.clientY) / 2;
|
|
4360
|
+
this.pinchTo(p, Math.min(Math.max(p.drawn, PINCH_MAX_SLOT), Math.max(Math.min(p.drawn, PINCH_MIN_SLOT), height)));
|
|
4361
|
+
};
|
|
4362
|
+
onTouchEnd = (ev) => {
|
|
4363
|
+
if (ev.touches.length < 2) this.endPinch();
|
|
4221
4364
|
};
|
|
4365
|
+
/** Rescales the grid, keeping the anchored time under the fingers. */
|
|
4366
|
+
pinchTo(p, slotHeight) {
|
|
4367
|
+
this.opts = { ...this.opts, slotHeight };
|
|
4368
|
+
if (!(this.current instanceof TimeGridView)) return;
|
|
4369
|
+
this.current.applyScale();
|
|
4370
|
+
const { top, height } = p.body.getBoundingClientRect();
|
|
4371
|
+
const scroller = scrollerOf(p.body, "y");
|
|
4372
|
+
if (scroller) scroller.scrollTop += top + height * p.anchor - p.y;
|
|
4373
|
+
}
|
|
4374
|
+
endPinch() {
|
|
4375
|
+
const p = this.pinch;
|
|
4376
|
+
this.pinch = null;
|
|
4377
|
+
if (!p?.body.isConnected) return;
|
|
4378
|
+
const settled = Math.round(this.opts.slotHeight);
|
|
4379
|
+
const slotHeight = settled === Math.round(p.drawn) ? p.option : settled;
|
|
4380
|
+
this.pinchTo(p, slotHeight);
|
|
4381
|
+
this.current?.render(null);
|
|
4382
|
+
if (slotHeight !== p.option) this.opts.onSlotHeightChange?.(slotHeight);
|
|
4383
|
+
}
|
|
4222
4384
|
onPointerMove = (ev) => {
|
|
4223
4385
|
const s = this.session;
|
|
4224
4386
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
@@ -4568,6 +4730,7 @@ var Calendar = class {
|
|
|
4568
4730
|
scroller.scrollLeft = position.left;
|
|
4569
4731
|
}
|
|
4570
4732
|
this.dragScrollOrigins.clear();
|
|
4733
|
+
this.dragScrollEdges.clear();
|
|
4571
4734
|
this.session = null;
|
|
4572
4735
|
this.listDragPreview?.element.remove();
|
|
4573
4736
|
this.listDragPreview = null;
|
|
@@ -4586,10 +4749,15 @@ var Calendar = class {
|
|
|
4586
4749
|
);
|
|
4587
4750
|
const draggedId = this.draggingId;
|
|
4588
4751
|
this.draggingId = null;
|
|
4752
|
+
this.dragWidth = null;
|
|
4589
4753
|
this.dragRow = null;
|
|
4590
|
-
if (draggedId) for (const el of this.elementsFor(draggedId))
|
|
4754
|
+
if (draggedId) for (const el of this.elementsFor(draggedId)) {
|
|
4755
|
+
el.classList.remove("is-dragging");
|
|
4756
|
+
el.style.removeProperty("--tnc-drag-width");
|
|
4757
|
+
}
|
|
4591
4758
|
this.strip?.markTarget(null);
|
|
4592
4759
|
if (!s.active) return;
|
|
4760
|
+
if (this.current instanceof DayGridView) this.current.render(null);
|
|
4593
4761
|
if (s.mode === "select") {
|
|
4594
4762
|
if (cancel) this.unselectSilently();
|
|
4595
4763
|
else if (this.selection) this.fireSelect(this.selection, ev);
|