@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/index.cjs
CHANGED
|
@@ -502,43 +502,44 @@ function segmentInWindow(n, day, minMinute, maxMinute, zone) {
|
|
|
502
502
|
};
|
|
503
503
|
}
|
|
504
504
|
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);
|
|
505
|
+
function collides(a, b, minMinutes) {
|
|
506
|
+
if (b.startMin < a.endMin) return true;
|
|
507
|
+
if (b.startMin === a.endMin && a.endMin > a.startMin) return false;
|
|
508
|
+
return b.startMin < a.startMin + minMinutes;
|
|
509
|
+
}
|
|
510
|
+
function overlaps(a, b, minMinutes) {
|
|
511
|
+
return a.startMin <= b.startMin ? collides(a, b, minMinutes) : collides(b, a, minMinutes);
|
|
512
|
+
}
|
|
505
513
|
function packTimed(segs, minMinutes, order = defaultOrder) {
|
|
506
|
-
const sorted = segs.
|
|
514
|
+
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));
|
|
507
515
|
const out = [];
|
|
508
516
|
let cluster = [];
|
|
509
|
-
let
|
|
510
|
-
let clusterEnd = -Infinity;
|
|
517
|
+
let colLast = [];
|
|
511
518
|
let clusterIndex = 0;
|
|
512
519
|
const flush = () => {
|
|
513
520
|
if (cluster.length) clusterIndex++;
|
|
514
|
-
const cols =
|
|
521
|
+
const cols = colLast.length;
|
|
515
522
|
for (const p of cluster) {
|
|
516
523
|
p.cols = cols;
|
|
517
524
|
let span = 1;
|
|
518
|
-
while (p.col + span < cols && !cluster.some((o) => o.col === p.col + span && o
|
|
525
|
+
while (p.col + span < cols && !cluster.some((o) => o.col === p.col + span && overlaps(o, p, minMinutes))) span++;
|
|
519
526
|
p.span = span;
|
|
520
527
|
}
|
|
521
528
|
cluster = [];
|
|
522
|
-
|
|
529
|
+
colLast = [];
|
|
523
530
|
};
|
|
524
531
|
for (const s of sorted) {
|
|
525
|
-
if (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
let col = colEnds.findIndex((end) => end <= s.startMin);
|
|
530
|
-
if (col < 0) {
|
|
531
|
-
col = colEnds.length;
|
|
532
|
-
colEnds.push(0);
|
|
533
|
-
}
|
|
534
|
-
colEnds[col] = s.end;
|
|
532
|
+
if (!cluster.some((o) => collides(o, s, minMinutes))) flush();
|
|
533
|
+
let col = colLast.findIndex((last) => !collides(last, s, minMinutes));
|
|
534
|
+
if (col < 0) col = colLast.length;
|
|
535
535
|
const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex, split: false };
|
|
536
|
+
colLast[col] = p;
|
|
536
537
|
cluster.push(p);
|
|
537
538
|
out.push(p);
|
|
538
|
-
clusterEnd = Math.max(clusterEnd, s.end);
|
|
539
539
|
}
|
|
540
540
|
flush();
|
|
541
|
-
|
|
541
|
+
const starts = new Set(out.map((p) => p.startMin));
|
|
542
|
+
for (const p of out) if (p.endMin > p.startMin && p.startMin + minMinutes > p.endMin && starts.has(p.endMin)) p.abutted = true;
|
|
542
543
|
return out;
|
|
543
544
|
}
|
|
544
545
|
function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTES_PER_DAY]) {
|
|
@@ -560,15 +561,8 @@ function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTE
|
|
|
560
561
|
}
|
|
561
562
|
function markSplit(lane, other, minMinutes) {
|
|
562
563
|
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
564
|
const split = /* @__PURE__ */ new Set();
|
|
571
|
-
for (const
|
|
565
|
+
for (const p of lane) if (!split.has(p.cluster) && other.some((o) => overlaps(p, o, minMinutes))) split.add(p.cluster);
|
|
572
566
|
for (const p of lane) p.split = split.has(p.cluster);
|
|
573
567
|
}
|
|
574
568
|
function layoutRow(items, days, order = defaultOrder) {
|
|
@@ -832,14 +826,16 @@ function pct(n) {
|
|
|
832
826
|
|
|
833
827
|
// src/dom/format.ts
|
|
834
828
|
var Formatter = class {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
hour12;
|
|
838
|
-
constructor(locale, hour12) {
|
|
829
|
+
constructor(locale, hour12, formatTime) {
|
|
830
|
+
this.formatTime = formatTime;
|
|
839
831
|
this.locale = locale;
|
|
840
832
|
const probe = new Intl.DateTimeFormat(locale, { hour: "numeric" }).resolvedOptions();
|
|
841
833
|
this.hour12 = hour12 ?? (probe.hour12 === true || probe.hourCycle === "h12" || probe.hourCycle === "h11");
|
|
842
834
|
}
|
|
835
|
+
formatTime;
|
|
836
|
+
cache = /* @__PURE__ */ new Map();
|
|
837
|
+
locale;
|
|
838
|
+
hour12;
|
|
843
839
|
fmt(key, opts) {
|
|
844
840
|
let f = this.cache.get(key);
|
|
845
841
|
if (!f) {
|
|
@@ -897,6 +893,7 @@ var Formatter = class {
|
|
|
897
893
|
/** Wall-clock minutes to a label. `compact` drops ":00" in 12-hour formats ("9am"). */
|
|
898
894
|
time(minute, compact = false) {
|
|
899
895
|
const m = (Math.round(minute) % 1440 + 1440) % 1440;
|
|
896
|
+
if (this.formatTime) return this.formatTime(m);
|
|
900
897
|
const date = new Date(Date.UTC(2e3, 0, 1, Math.floor(m / 60), m % 60));
|
|
901
898
|
if (this.hour12) {
|
|
902
899
|
const parts = this.fmt("t12", { hour: "numeric", minute: "2-digit", hour12: true }).formatToParts(date);
|
|
@@ -988,6 +985,7 @@ function defaultOptions() {
|
|
|
988
985
|
snapDuration: void 0,
|
|
989
986
|
scrollTime: "08:00",
|
|
990
987
|
slotHeight: 24,
|
|
988
|
+
pinchZoom: true,
|
|
991
989
|
expandRows: false,
|
|
992
990
|
nowIndicator: true,
|
|
993
991
|
selectMirror: true,
|
|
@@ -1029,6 +1027,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
1029
1027
|
"locale",
|
|
1030
1028
|
"firstDay",
|
|
1031
1029
|
"hour12",
|
|
1030
|
+
"formatTime",
|
|
1032
1031
|
"weekNumbers",
|
|
1033
1032
|
"weekends",
|
|
1034
1033
|
"hiddenDays",
|
|
@@ -1041,9 +1040,11 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
1041
1040
|
"slotDuration",
|
|
1042
1041
|
"snapDuration",
|
|
1043
1042
|
"slotHeight",
|
|
1043
|
+
"pinchZoom",
|
|
1044
1044
|
"expandRows",
|
|
1045
1045
|
"nowIndicator",
|
|
1046
1046
|
"slotEventOverlap",
|
|
1047
|
+
"overlapStackThreshold",
|
|
1047
1048
|
"eventMinHeight",
|
|
1048
1049
|
"eventMaxStack",
|
|
1049
1050
|
"dayMaxEvents",
|
|
@@ -1158,6 +1159,7 @@ var DayGridView = class {
|
|
|
1158
1159
|
topPx = 24;
|
|
1159
1160
|
lastHeights = "";
|
|
1160
1161
|
dotsMode;
|
|
1162
|
+
dragPreviews = [];
|
|
1161
1163
|
daysPerWeek() {
|
|
1162
1164
|
const { days } = this.ctx;
|
|
1163
1165
|
const first = days[0];
|
|
@@ -1171,6 +1173,36 @@ var DayGridView = class {
|
|
|
1171
1173
|
}
|
|
1172
1174
|
render(_change) {
|
|
1173
1175
|
const { ctx } = this;
|
|
1176
|
+
this.clearDragPreviews();
|
|
1177
|
+
const dragged = ctx.draggingId ? ctx.store.get(ctx.draggingId) : void 0;
|
|
1178
|
+
const source = dragged ? this.elementsFor(dragged.id)[0] : void 0;
|
|
1179
|
+
if (dragged && source && this.el.closest(".tnc-dragging-move")) {
|
|
1180
|
+
const proposals = [dragged, ...[...ctx.store.all()].filter((item) => item.id !== dragged.id && item.item.preview)];
|
|
1181
|
+
for (const proposal of proposals) {
|
|
1182
|
+
const original = this.elementsFor(proposal.id)[0] ?? source;
|
|
1183
|
+
const bounds = original.getBoundingClientRect();
|
|
1184
|
+
const sourceWeek = original.closest(".tnc-dg-week");
|
|
1185
|
+
const offset = bounds.top - sourceWeek.getBoundingClientRect().top;
|
|
1186
|
+
for (const week of this.weeks) {
|
|
1187
|
+
const start = week.days.findIndex((day) => day >= proposal.startDay && day < proposal.endDay);
|
|
1188
|
+
if (start < 0) continue;
|
|
1189
|
+
let end = start + 1;
|
|
1190
|
+
while (!this.dotsMode && end < week.days.length && week.days[end] < proposal.endDay) end++;
|
|
1191
|
+
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 });
|
|
1192
|
+
preview.classList.add("tnc-month-preview");
|
|
1193
|
+
preview.inert = true;
|
|
1194
|
+
setStyle(preview, {
|
|
1195
|
+
left: `calc(${pct(start / week.days.length)} + 2px)`,
|
|
1196
|
+
width: `calc(${pct((end - start) / week.days.length)} - 4px)`,
|
|
1197
|
+
top: `${Math.max(this.topPx, Math.min(offset, week.row.clientHeight - bounds.height - 4))}px`,
|
|
1198
|
+
height: `${bounds.height}px`
|
|
1199
|
+
});
|
|
1200
|
+
week.layer.append(preview);
|
|
1201
|
+
this.dragPreviews.push(preview);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1174
1206
|
this.dotsMode = ctx.opts.monthDisplay !== "full" || this.grid.clientWidth / this.daysPerWeek() < 72;
|
|
1175
1207
|
this.el.classList.toggle("tnc-dg--dots", this.dotsMode);
|
|
1176
1208
|
this.el.classList.toggle("tnc-dg--compact", this.dotsMode && ctx.opts.monthDisplay === "full");
|
|
@@ -1202,6 +1234,10 @@ var DayGridView = class {
|
|
|
1202
1234
|
}
|
|
1203
1235
|
}
|
|
1204
1236
|
}
|
|
1237
|
+
clearDragPreviews() {
|
|
1238
|
+
for (const preview of this.dragPreviews) this.ctx.destroyItemEl(preview);
|
|
1239
|
+
this.dragPreviews = [];
|
|
1240
|
+
}
|
|
1205
1241
|
/** Height of a "+N more" link as the host styles it (`--tnc-more-h`, or a taller min-height). */
|
|
1206
1242
|
measureMore(cs) {
|
|
1207
1243
|
const fallback = px(cs, "--tnc-more-h", this.barPx);
|
|
@@ -1393,6 +1429,7 @@ var DayGridView = class {
|
|
|
1393
1429
|
return this.weeks.flatMap((w) => [...w.pool.elementsFor(id), ...(w.dots ?? []).flatMap((c) => c.pool.elementsFor(id))]);
|
|
1394
1430
|
}
|
|
1395
1431
|
destroy() {
|
|
1432
|
+
this.clearDragPreviews();
|
|
1396
1433
|
this.resizeObs?.disconnect();
|
|
1397
1434
|
for (const w of this.weeks) {
|
|
1398
1435
|
w.pool.clear();
|
|
@@ -1445,6 +1482,7 @@ var ListView = class {
|
|
|
1445
1482
|
frame = null;
|
|
1446
1483
|
/** A picked day stays marked until the list is scrolled by hand. */
|
|
1447
1484
|
pinned = false;
|
|
1485
|
+
pinnedTop = 0;
|
|
1448
1486
|
itemsOn(day) {
|
|
1449
1487
|
const { store, opts } = this.ctx;
|
|
1450
1488
|
const out = store.spanningIn(day, day + 1, true);
|
|
@@ -1576,6 +1614,7 @@ var ListView = class {
|
|
|
1576
1614
|
this.scroller.scrollTop += section.getBoundingClientRect().top - this.scroller.getBoundingClientRect().top;
|
|
1577
1615
|
this.ctx.strip?.setFocus(day);
|
|
1578
1616
|
this.pinned = true;
|
|
1617
|
+
this.pinnedTop = this.scroller.scrollTop;
|
|
1579
1618
|
}
|
|
1580
1619
|
fold = () => {
|
|
1581
1620
|
this.pinned = false;
|
|
@@ -1583,6 +1622,7 @@ var ListView = class {
|
|
|
1583
1622
|
if (strip?.expanded) strip.setExpanded(false);
|
|
1584
1623
|
};
|
|
1585
1624
|
onScroll = () => {
|
|
1625
|
+
if (this.pinned && Math.abs(this.scroller.scrollTop - this.pinnedTop) > 1) this.pinned = false;
|
|
1586
1626
|
if (this.frame !== null) return;
|
|
1587
1627
|
this.frame = this.ctx.win.requestAnimationFrame(() => {
|
|
1588
1628
|
this.frame = null;
|
|
@@ -1593,7 +1633,7 @@ var ListView = class {
|
|
|
1593
1633
|
followTop() {
|
|
1594
1634
|
const strip = this.ctx.strip;
|
|
1595
1635
|
if (!strip || this.pinned) return;
|
|
1596
|
-
const edge = strip.el.getBoundingClientRect().bottom + 1;
|
|
1636
|
+
const edge = Math.max(strip.el.getBoundingClientRect().bottom, this.scroller.getBoundingClientRect().top) + 1;
|
|
1597
1637
|
for (const day of this.ctx.days) {
|
|
1598
1638
|
const section = this.sections.get(day);
|
|
1599
1639
|
if (section && section.getBoundingClientRect().bottom > edge) {
|
|
@@ -1641,10 +1681,11 @@ var ListView = class {
|
|
|
1641
1681
|
// src/dom/views/strip.ts
|
|
1642
1682
|
var SWIPE_PX = 40;
|
|
1643
1683
|
var DateStrip = class {
|
|
1644
|
-
constructor(ctx, pick, focus, onShown) {
|
|
1684
|
+
constructor(ctx, pick, focus, onShown, onPage) {
|
|
1645
1685
|
this.ctx = ctx;
|
|
1646
1686
|
this.pick = pick;
|
|
1647
1687
|
this.onShown = onShown;
|
|
1688
|
+
this.onPage = onPage;
|
|
1648
1689
|
const { doc, text } = ctx;
|
|
1649
1690
|
this.focus = focus;
|
|
1650
1691
|
this.shown = startOfWeek(focus, ctx.firstDay);
|
|
@@ -1668,6 +1709,10 @@ var DateStrip = class {
|
|
|
1668
1709
|
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
1669
1710
|
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
1670
1711
|
this.el.addEventListener("pointerup", this.onPointerUp);
|
|
1712
|
+
this.el.addEventListener("touchstart", this.ownTouch, { passive: true });
|
|
1713
|
+
this.el.addEventListener("touchmove", this.ownTouch, { passive: true });
|
|
1714
|
+
this.el.addEventListener("touchend", this.ownTouch, { passive: true });
|
|
1715
|
+
this.el.addEventListener("touchcancel", this.ownTouch, { passive: true });
|
|
1671
1716
|
this.el.addEventListener("pointercancel", this.onPointerCancel);
|
|
1672
1717
|
this.el.addEventListener("click", this.onClickCapture, true);
|
|
1673
1718
|
this.draw();
|
|
@@ -1676,6 +1721,7 @@ var DateStrip = class {
|
|
|
1676
1721
|
ctx;
|
|
1677
1722
|
pick;
|
|
1678
1723
|
onShown;
|
|
1724
|
+
onPage;
|
|
1679
1725
|
el;
|
|
1680
1726
|
title;
|
|
1681
1727
|
dows;
|
|
@@ -1734,6 +1780,7 @@ var DateStrip = class {
|
|
|
1734
1780
|
step(dir) {
|
|
1735
1781
|
this.shown = this.expanded ? addMonths(this.shown, dir) : this.shown + 7 * dir;
|
|
1736
1782
|
this.draw();
|
|
1783
|
+
this.onPage?.(this.expanded ? this.shown : this.focus + 7 * dir);
|
|
1737
1784
|
}
|
|
1738
1785
|
days() {
|
|
1739
1786
|
if (!this.expanded) return Array.from({ length: 7 }, (_, i) => this.shown + i);
|
|
@@ -1758,7 +1805,8 @@ var DateStrip = class {
|
|
|
1758
1805
|
const tabDay = days.includes(this.focus) ? this.focus : days[0];
|
|
1759
1806
|
const cells = days.map((d) => {
|
|
1760
1807
|
const arg = ctx.dayArg(d);
|
|
1761
|
-
const
|
|
1808
|
+
const items = store.inRange(d, d + 1).filter((n) => n.item.display !== "background" && n.item.display !== "none");
|
|
1809
|
+
const count = items.length;
|
|
1762
1810
|
const first = ymdOfDay(d).day === 1 && d !== days[0] && !this.expanded;
|
|
1763
1811
|
const label = fmt.longDate(d);
|
|
1764
1812
|
const cell = h(
|
|
@@ -1782,7 +1830,15 @@ var DateStrip = class {
|
|
|
1782
1830
|
}
|
|
1783
1831
|
);
|
|
1784
1832
|
if (first) cell.append(h(doc, "span", "tnc-strip-month", { "aria-hidden": "true" }, fmt.monthShort(d)));
|
|
1785
|
-
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d))
|
|
1833
|
+
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d)));
|
|
1834
|
+
const dots = h(doc, "span", "tnc-strip-dots", { "aria-hidden": "true" });
|
|
1835
|
+
for (const item of items.slice(0, 3)) {
|
|
1836
|
+
const dot = h(doc, "span", "tnc-strip-dot");
|
|
1837
|
+
const color = item.item.color || item.item.borderColor || item.item.backgroundColor;
|
|
1838
|
+
if (color) dot.style.backgroundColor = color;
|
|
1839
|
+
dots.append(dot);
|
|
1840
|
+
}
|
|
1841
|
+
cell.append(dots);
|
|
1786
1842
|
return cell;
|
|
1787
1843
|
});
|
|
1788
1844
|
this.grid.replaceChildren(...cells);
|
|
@@ -1822,6 +1878,9 @@ var DateStrip = class {
|
|
|
1822
1878
|
if (ev.button !== 0 || this.ctx.draggingId) return;
|
|
1823
1879
|
this.swipe = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
|
|
1824
1880
|
};
|
|
1881
|
+
ownTouch = (event) => {
|
|
1882
|
+
event.stopPropagation();
|
|
1883
|
+
};
|
|
1825
1884
|
onPointerUp = (ev) => {
|
|
1826
1885
|
const s = this.swipe;
|
|
1827
1886
|
this.swipe = null;
|
|
@@ -1861,6 +1920,7 @@ var TimeGridView = class {
|
|
|
1861
1920
|
const showGrid = opts.timeGrid;
|
|
1862
1921
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1863
1922
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1923
|
+
this.el.classList.toggle("tnc-tg--pinch", showGrid && opts.pinchZoom);
|
|
1864
1924
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1865
1925
|
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1866
1926
|
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
@@ -2010,6 +2070,11 @@ var TimeGridView = class {
|
|
|
2010
2070
|
}
|
|
2011
2071
|
if (this.nowGutter) gutter.append(this.nowGutter);
|
|
2012
2072
|
}
|
|
2073
|
+
/** Slot height as drawn, which `expandRows` can stretch past the option. */
|
|
2074
|
+
get drawnSlotHeight() {
|
|
2075
|
+
return this.ctx.slot.duration * this.ppm;
|
|
2076
|
+
}
|
|
2077
|
+
/** Applies `slotHeight`. Items are placed in percentages, so they follow without a render. */
|
|
2013
2078
|
applyScale() {
|
|
2014
2079
|
const { opts, slot } = this.ctx;
|
|
2015
2080
|
this.ppm = opts.slotHeight / slot.duration;
|
|
@@ -2190,15 +2255,17 @@ var TimeGridView = class {
|
|
|
2190
2255
|
const { opts } = ctx;
|
|
2191
2256
|
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
2192
2257
|
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
2193
|
-
const
|
|
2258
|
+
const overlapThreshold = opts.overlapStackThreshold ?? 0;
|
|
2259
|
+
const collapsedClusters = new Set(placements.filter((placement) => {
|
|
2194
2260
|
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2261
|
+
const denseOverlap = overlapThreshold > 1 && placement.cols >= overlapThreshold;
|
|
2262
|
+
return columns > 1 && (columns * readableWidth > laneWidth || denseOverlap);
|
|
2263
|
+
}).map((placement) => placement.cluster));
|
|
2199
2264
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
2200
2265
|
pool.begin();
|
|
2201
2266
|
for (const p of placements) {
|
|
2267
|
+
const withMore = lane === "plan" || collapsedClusters.has(p.cluster);
|
|
2268
|
+
const maxStack = collapsedClusters.has(p.cluster) ? 1 : opts.eventMaxStack;
|
|
2202
2269
|
const preview = p.item.item.preview || p.item.id === ctx.draggingId;
|
|
2203
2270
|
if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
|
|
2204
2271
|
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
|
|
@@ -2211,7 +2278,7 @@ var TimeGridView = class {
|
|
|
2211
2278
|
}
|
|
2212
2279
|
const cols = !preview && withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
|
|
2213
2280
|
const span = Math.min(p.span, cols - p.col);
|
|
2214
|
-
const left = p.col / cols;
|
|
2281
|
+
const left = p.item.id === ctx.draggingId ? 0 : p.col / cols;
|
|
2215
2282
|
let width = span / cols;
|
|
2216
2283
|
if (opts.slotEventOverlap && cols > 1 && p.col + span < cols) width = Math.min(1 - left, width * 1.7);
|
|
2217
2284
|
const el = pool.place(p.item.id, this.meta(p, false));
|
|
@@ -2219,15 +2286,16 @@ var TimeGridView = class {
|
|
|
2219
2286
|
el.classList.toggle("is-short", short);
|
|
2220
2287
|
setStyle(el, {
|
|
2221
2288
|
top: top(p.startMin),
|
|
2222
|
-
height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
2289
|
+
height: p.abutted ? height(p) : `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
2223
2290
|
...inLane(lane, p.split, left, width),
|
|
2224
2291
|
"--tnc-event-readable-width": pct(span / cols / width),
|
|
2225
2292
|
"z-index": String(p.col + 1)
|
|
2226
2293
|
});
|
|
2227
2294
|
}
|
|
2228
2295
|
pool.end();
|
|
2229
|
-
if (!withMore) return;
|
|
2230
2296
|
for (const [cluster, rec] of hiddenByCluster) {
|
|
2297
|
+
const collapse = collapsedClusters.has(cluster);
|
|
2298
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2231
2299
|
const key = `${lane}-${cluster}`;
|
|
2232
2300
|
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
2233
2301
|
if (!link) {
|
|
@@ -2393,7 +2461,9 @@ var TimeGridView = class {
|
|
|
2393
2461
|
scrollToTime(minute) {
|
|
2394
2462
|
if (!this.body) return;
|
|
2395
2463
|
const y = (minute - this.ctx.slot.min) * this.ppm;
|
|
2396
|
-
|
|
2464
|
+
const label = this.gutter?.querySelector(".tnc-slot-label");
|
|
2465
|
+
const inset = label ? Math.ceil(label.getBoundingClientRect().height / 2) + 2 : 0;
|
|
2466
|
+
this.scroller.scrollTop = Math.max(0, y - inset);
|
|
2397
2467
|
}
|
|
2398
2468
|
getScroll() {
|
|
2399
2469
|
return { top: this.scroller.scrollTop, left: this.scroller.scrollLeft };
|
|
@@ -3057,7 +3127,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
3057
3127
|
const bounds = month.getBoundingClientRect();
|
|
3058
3128
|
const viewport = this.scroller.getBoundingClientRect();
|
|
3059
3129
|
if (bounds.top < viewport.top) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
3060
|
-
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.
|
|
3130
|
+
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
3061
3131
|
}
|
|
3062
3132
|
elementsFor(_id) {
|
|
3063
3133
|
return [];
|
|
@@ -3071,6 +3141,8 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
3071
3141
|
var EXTERNAL_ID = "__tnc_external__";
|
|
3072
3142
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
3073
3143
|
var MAX_AGENDA_DAYS = 366;
|
|
3144
|
+
var PINCH_MIN_SLOT = 16;
|
|
3145
|
+
var PINCH_MAX_SLOT = 96;
|
|
3074
3146
|
function sameOption(a, b, depth = 0) {
|
|
3075
3147
|
if (a === b) return true;
|
|
3076
3148
|
if (typeof a === "function" && typeof b === "function") return true;
|
|
@@ -3098,7 +3170,7 @@ var Calendar = class {
|
|
|
3098
3170
|
this.win = this.doc.defaultView ?? globalThis;
|
|
3099
3171
|
this.opts = { ...defaultOptions(), ...options };
|
|
3100
3172
|
this.zone = getTimeZone(this.opts.timeZone);
|
|
3101
|
-
this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
3173
|
+
this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
3102
3174
|
this.text = this.resolveText();
|
|
3103
3175
|
this.order = parseOrder(this.opts.eventOrder);
|
|
3104
3176
|
this.store = new ItemStore(this.zone, {
|
|
@@ -3140,13 +3212,16 @@ var Calendar = class {
|
|
|
3140
3212
|
draggingId = null;
|
|
3141
3213
|
listDragPreview = null;
|
|
3142
3214
|
dragSources = [];
|
|
3215
|
+
dragWidth = null;
|
|
3143
3216
|
dragScrollFrame = null;
|
|
3144
3217
|
dragScrollOrigins = /* @__PURE__ */ new Map();
|
|
3218
|
+
dragScrollEdges = /* @__PURE__ */ new Map();
|
|
3145
3219
|
itemWriteStates = /* @__PURE__ */ new Map();
|
|
3146
3220
|
dragRow = null;
|
|
3147
3221
|
strip = null;
|
|
3148
3222
|
stripSize = null;
|
|
3149
3223
|
rebuilding = false;
|
|
3224
|
+
pinch = null;
|
|
3150
3225
|
toolbarEl;
|
|
3151
3226
|
viewHost;
|
|
3152
3227
|
current = null;
|
|
@@ -3255,7 +3330,7 @@ var Calendar = class {
|
|
|
3255
3330
|
defaultAllDayDays: this.opts.defaultAllDayDays
|
|
3256
3331
|
});
|
|
3257
3332
|
}
|
|
3258
|
-
if (has("locale") || has("hour12")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
3333
|
+
if (has("locale") || has("hour12") || has("formatTime")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
3259
3334
|
if (has("text") || has("locale")) this.text = this.resolveText();
|
|
3260
3335
|
if (has("eventOrder")) this.order = parseOrder(this.opts.eventOrder);
|
|
3261
3336
|
if (has("date")) this.anchor = this.toDay(this.opts.date) ?? this.anchor;
|
|
@@ -3494,7 +3569,10 @@ var Calendar = class {
|
|
|
3494
3569
|
if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
|
|
3495
3570
|
const indicator = variant === "indicator";
|
|
3496
3571
|
if (!preview && !indicator && (this.isEditable(n, "start", true) || this.isEditable(n, "duration", true))) el.classList.add("is-draggable");
|
|
3497
|
-
if (this.draggingId === n.id)
|
|
3572
|
+
if (this.draggingId === n.id) {
|
|
3573
|
+
el.classList.add("is-dragging");
|
|
3574
|
+
if (variant === "timed" && this.dragWidth !== null) el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
3575
|
+
}
|
|
3498
3576
|
if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
|
|
3499
3577
|
el.classList.add(...classList(it.className));
|
|
3500
3578
|
const arg = this.itemArg(el, meta);
|
|
@@ -3762,7 +3840,9 @@ var Calendar = class {
|
|
|
3762
3840
|
this.root.classList.add("tnc--has-strip");
|
|
3763
3841
|
const { startDay, endDay } = this.view.range;
|
|
3764
3842
|
if (!this.strip) {
|
|
3765
|
-
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown())
|
|
3843
|
+
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown(), (day) => {
|
|
3844
|
+
if (this.agendaList()) this.showListDay(day);
|
|
3845
|
+
});
|
|
3766
3846
|
this.strip = strip;
|
|
3767
3847
|
this.root.insertBefore(strip.el, this.viewHost);
|
|
3768
3848
|
const RO = this.win.ResizeObserver;
|
|
@@ -4126,7 +4206,10 @@ var Calendar = class {
|
|
|
4126
4206
|
r.addEventListener("dragover", this.onNativeDragOver);
|
|
4127
4207
|
r.addEventListener("dragleave", this.onNativeDragLeave);
|
|
4128
4208
|
r.addEventListener("drop", this.onNativeDrop);
|
|
4209
|
+
r.addEventListener("touchstart", this.onTouchStart, { passive: true });
|
|
4129
4210
|
r.addEventListener("touchmove", this.onTouchMove, { passive: false });
|
|
4211
|
+
r.addEventListener("touchend", this.onTouchEnd);
|
|
4212
|
+
r.addEventListener("touchcancel", this.onTouchEnd);
|
|
4130
4213
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
4131
4214
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
4132
4215
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -4145,7 +4228,10 @@ var Calendar = class {
|
|
|
4145
4228
|
r.removeEventListener("dragover", this.onNativeDragOver);
|
|
4146
4229
|
r.removeEventListener("dragleave", this.onNativeDragLeave);
|
|
4147
4230
|
r.removeEventListener("drop", this.onNativeDrop);
|
|
4231
|
+
r.removeEventListener("touchstart", this.onTouchStart);
|
|
4148
4232
|
r.removeEventListener("touchmove", this.onTouchMove);
|
|
4233
|
+
r.removeEventListener("touchend", this.onTouchEnd);
|
|
4234
|
+
r.removeEventListener("touchcancel", this.onTouchEnd);
|
|
4149
4235
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
4150
4236
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
4151
4237
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -4240,7 +4326,11 @@ var Calendar = class {
|
|
|
4240
4326
|
if (s.origin) {
|
|
4241
4327
|
this.draggingId = s.origin.id;
|
|
4242
4328
|
for (const el of this.elementsFor(s.origin.id)) {
|
|
4243
|
-
if (this.
|
|
4329
|
+
if (this.meta.get(el)?.variant === "timed" && s.mode === "move") {
|
|
4330
|
+
this.dragWidth = el.getBoundingClientRect().width;
|
|
4331
|
+
el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
4332
|
+
}
|
|
4333
|
+
if (this.opts.view !== "list" && !(this.current instanceof DayGridView) && s.mode === "move") {
|
|
4244
4334
|
const source = el.cloneNode(true);
|
|
4245
4335
|
source.classList.remove("is-selected");
|
|
4246
4336
|
source.classList.add("tnc-drag-source");
|
|
@@ -4293,24 +4383,96 @@ var Calendar = class {
|
|
|
4293
4383
|
const coordinate = axis === "x" ? pointer.clientX : pointer.clientY;
|
|
4294
4384
|
const lower = Math.max(0, axis === "x" ? viewport.left : viewport.top);
|
|
4295
4385
|
const upper = Math.min(axis === "x" ? this.win.innerWidth : this.win.innerHeight, axis === "x" ? viewport.right : viewport.bottom);
|
|
4296
|
-
if (coordinate < lower || coordinate > upper)
|
|
4386
|
+
if (coordinate < lower || coordinate > upper) {
|
|
4387
|
+
this.dragScrollEdges.delete(axis);
|
|
4388
|
+
continue;
|
|
4389
|
+
}
|
|
4297
4390
|
const delta = coordinate < lower + 32 ? -Math.min(12, (lower + 32 - coordinate) / 2) : coordinate > upper - 32 ? Math.min(12, (coordinate - upper + 32) / 2) : 0;
|
|
4298
|
-
if (!delta)
|
|
4391
|
+
if (!delta) {
|
|
4392
|
+
this.dragScrollEdges.delete(axis);
|
|
4393
|
+
continue;
|
|
4394
|
+
}
|
|
4395
|
+
const edge = this.dragScrollEdges.get(axis);
|
|
4396
|
+
const direction = Math.sign(delta);
|
|
4397
|
+
const now = this.win.performance.now();
|
|
4398
|
+
if (!edge || edge.scroller !== scroller || edge.direction !== direction) {
|
|
4399
|
+
this.dragScrollEdges.set(axis, { scroller, direction, since: now });
|
|
4400
|
+
continue;
|
|
4401
|
+
}
|
|
4402
|
+
if (now - edge.since < 180) continue;
|
|
4299
4403
|
if (!this.dragScrollOrigins.has(scroller)) this.dragScrollOrigins.set(scroller, { top: scroller.scrollTop, left: scroller.scrollLeft });
|
|
4300
4404
|
const before = axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
|
|
4301
4405
|
if (axis === "x") scroller.scrollLeft += delta;
|
|
4302
4406
|
else scroller.scrollTop += delta;
|
|
4303
4407
|
moved ||= before !== (axis === "x" ? scroller.scrollLeft : scroller.scrollTop);
|
|
4304
4408
|
}
|
|
4305
|
-
}
|
|
4409
|
+
} else this.dragScrollEdges.clear();
|
|
4306
4410
|
if (moved) this.track(session, pointer);
|
|
4307
4411
|
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4308
4412
|
};
|
|
4309
4413
|
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4310
4414
|
}
|
|
4415
|
+
onTouchStart = (ev) => {
|
|
4416
|
+
const first = ev.touches[0];
|
|
4417
|
+
const second = ev.touches[1];
|
|
4418
|
+
if (!first || !second) {
|
|
4419
|
+
this.endPinch();
|
|
4420
|
+
return;
|
|
4421
|
+
}
|
|
4422
|
+
const view = this.current;
|
|
4423
|
+
if (this.session?.active || !this.opts.pinchZoom || !(view instanceof TimeGridView)) return;
|
|
4424
|
+
const body = first.target.closest(".tnc-tg-body");
|
|
4425
|
+
if (!body?.contains(second.target)) return;
|
|
4426
|
+
this.endSession(null, true);
|
|
4427
|
+
if (this.pinch) return;
|
|
4428
|
+
const { top, height } = body.getBoundingClientRect();
|
|
4429
|
+
const y = (first.clientY + second.clientY) / 2;
|
|
4430
|
+
this.pinch = {
|
|
4431
|
+
body,
|
|
4432
|
+
distance: Math.max(1, Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY)),
|
|
4433
|
+
y,
|
|
4434
|
+
anchor: (y - top) / height,
|
|
4435
|
+
drawn: view.drawnSlotHeight,
|
|
4436
|
+
option: this.opts.slotHeight
|
|
4437
|
+
};
|
|
4438
|
+
};
|
|
4311
4439
|
onTouchMove = (ev) => {
|
|
4312
4440
|
if (this.session?.active) ev.preventDefault();
|
|
4441
|
+
const p = this.pinch;
|
|
4442
|
+
const first = ev.touches[0];
|
|
4443
|
+
const second = ev.touches[1];
|
|
4444
|
+
if (!p || !first || !second) return;
|
|
4445
|
+
if (!ev.cancelable) {
|
|
4446
|
+
this.endPinch();
|
|
4447
|
+
return;
|
|
4448
|
+
}
|
|
4449
|
+
ev.preventDefault();
|
|
4450
|
+
const height = p.drawn * Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY) / p.distance;
|
|
4451
|
+
p.y = (first.clientY + second.clientY) / 2;
|
|
4452
|
+
this.pinchTo(p, Math.min(Math.max(p.drawn, PINCH_MAX_SLOT), Math.max(Math.min(p.drawn, PINCH_MIN_SLOT), height)));
|
|
4453
|
+
};
|
|
4454
|
+
onTouchEnd = (ev) => {
|
|
4455
|
+
if (ev.touches.length < 2) this.endPinch();
|
|
4313
4456
|
};
|
|
4457
|
+
/** Rescales the grid, keeping the anchored time under the fingers. */
|
|
4458
|
+
pinchTo(p, slotHeight) {
|
|
4459
|
+
this.opts = { ...this.opts, slotHeight };
|
|
4460
|
+
if (!(this.current instanceof TimeGridView)) return;
|
|
4461
|
+
this.current.applyScale();
|
|
4462
|
+
const { top, height } = p.body.getBoundingClientRect();
|
|
4463
|
+
const scroller = scrollerOf(p.body, "y");
|
|
4464
|
+
if (scroller) scroller.scrollTop += top + height * p.anchor - p.y;
|
|
4465
|
+
}
|
|
4466
|
+
endPinch() {
|
|
4467
|
+
const p = this.pinch;
|
|
4468
|
+
this.pinch = null;
|
|
4469
|
+
if (!p?.body.isConnected) return;
|
|
4470
|
+
const settled = Math.round(this.opts.slotHeight);
|
|
4471
|
+
const slotHeight = settled === Math.round(p.drawn) ? p.option : settled;
|
|
4472
|
+
this.pinchTo(p, slotHeight);
|
|
4473
|
+
this.current?.render(null);
|
|
4474
|
+
if (slotHeight !== p.option) this.opts.onSlotHeightChange?.(slotHeight);
|
|
4475
|
+
}
|
|
4314
4476
|
onPointerMove = (ev) => {
|
|
4315
4477
|
const s = this.session;
|
|
4316
4478
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
@@ -4660,6 +4822,7 @@ var Calendar = class {
|
|
|
4660
4822
|
scroller.scrollLeft = position.left;
|
|
4661
4823
|
}
|
|
4662
4824
|
this.dragScrollOrigins.clear();
|
|
4825
|
+
this.dragScrollEdges.clear();
|
|
4663
4826
|
this.session = null;
|
|
4664
4827
|
this.listDragPreview?.element.remove();
|
|
4665
4828
|
this.listDragPreview = null;
|
|
@@ -4678,10 +4841,15 @@ var Calendar = class {
|
|
|
4678
4841
|
);
|
|
4679
4842
|
const draggedId = this.draggingId;
|
|
4680
4843
|
this.draggingId = null;
|
|
4844
|
+
this.dragWidth = null;
|
|
4681
4845
|
this.dragRow = null;
|
|
4682
|
-
if (draggedId) for (const el of this.elementsFor(draggedId))
|
|
4846
|
+
if (draggedId) for (const el of this.elementsFor(draggedId)) {
|
|
4847
|
+
el.classList.remove("is-dragging");
|
|
4848
|
+
el.style.removeProperty("--tnc-drag-width");
|
|
4849
|
+
}
|
|
4683
4850
|
this.strip?.markTarget(null);
|
|
4684
4851
|
if (!s.active) return;
|
|
4852
|
+
if (this.current instanceof DayGridView) this.current.render(null);
|
|
4685
4853
|
if (s.mode === "select") {
|
|
4686
4854
|
if (cancel) this.unselectSilently();
|
|
4687
4855
|
else if (this.selection) this.fireSelect(this.selection, ev);
|
|
@@ -5070,6 +5238,8 @@ var BASES_CALENDAR_DEFAULTS = {
|
|
|
5070
5238
|
slotDuration: "00:30:00",
|
|
5071
5239
|
snapDuration: "00:30:00",
|
|
5072
5240
|
scrollTime: "08:00:00",
|
|
5241
|
+
slotHeight: 24,
|
|
5242
|
+
pinchZoom: true,
|
|
5073
5243
|
nowIndicator: true,
|
|
5074
5244
|
showTimeGrid: true,
|
|
5075
5245
|
selectMirror: true,
|
|
@@ -5134,6 +5304,8 @@ function basesLayoutOptions(s, context = {}) {
|
|
|
5134
5304
|
slotDuration,
|
|
5135
5305
|
snapDuration: time("snapDuration") ?? slotDuration,
|
|
5136
5306
|
scrollTime: time("scrollTime", 48) ?? d.scrollTime,
|
|
5307
|
+
slotHeight: clamp(Math.round(num(s.slotHeight, d.slotHeight)), 5, 96),
|
|
5308
|
+
pinchZoom: pick("pinchZoom") !== false,
|
|
5137
5309
|
nowIndicator: pick("nowIndicator") !== false,
|
|
5138
5310
|
timeGrid: showTimeGrid,
|
|
5139
5311
|
selectMirror: pick("selectMirror") !== false,
|