@tasknotes/calendar 0.1.5 → 0.1.7
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 +24 -0
- package/README.md +31 -0
- package/dist/calendar.css +74 -19
- package/dist/cjs/core.cjs +19 -2
- package/dist/cjs/index.cjs +235 -41
- package/dist/cjs/react.cjs +235 -41
- package/dist/esm/{chunk-DVMZ5OXN.js → chunk-66A6EMCU.js} +19 -2
- package/dist/esm/{chunk-25TY2UVR.js → chunk-EDRCVOY7.js} +217 -40
- package/dist/esm/core.js +1 -1
- package/dist/esm/index.js +2 -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 +5 -1
- package/dist/types/dom/calendar.d.ts +5 -1
- package/dist/types/dom/context.d.cts +1 -1
- package/dist/types/dom/context.d.ts +1 -1
- package/dist/types/dom/options.d.cts +3 -0
- package/dist/types/dom/options.d.ts +3 -0
- package/dist/types/dom/views/timegrid.d.cts +4 -0
- package/dist/types/dom/views/timegrid.d.ts +4 -0
- package/package.json +1 -1
package/dist/cjs/react.cjs
CHANGED
|
@@ -696,6 +696,7 @@ var DEFAULT_TEXT = {
|
|
|
696
696
|
allDay: "All day",
|
|
697
697
|
noItems: "Nothing scheduled",
|
|
698
698
|
more: (n) => `+${n} more`,
|
|
699
|
+
appointments: (n) => `${n} appointments`,
|
|
699
700
|
itemCount: (n) => n === 1 ? "1 item" : `${n} items`,
|
|
700
701
|
weekNumber: (n) => `W${n}`,
|
|
701
702
|
views: { week: "Week", day: "Day", days: "Days", month: "Month", list: "List", year: "Year", timeline: "Timeline" },
|
|
@@ -765,6 +766,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
765
766
|
"dateStrip",
|
|
766
767
|
"listLeadTitle",
|
|
767
768
|
"listAddText",
|
|
769
|
+
"listEmptyContent",
|
|
768
770
|
"monthDisplay",
|
|
769
771
|
"timeZone",
|
|
770
772
|
"locale",
|
|
@@ -859,7 +861,7 @@ function packTimed(segs, minMinutes, order = defaultOrder) {
|
|
|
859
861
|
colEnds.push(0);
|
|
860
862
|
}
|
|
861
863
|
colEnds[col] = s.end;
|
|
862
|
-
const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex };
|
|
864
|
+
const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex, split: false };
|
|
863
865
|
cluster.push(p);
|
|
864
866
|
out.push(p);
|
|
865
867
|
clusterEnd = Math.max(clusterEnd, s.end);
|
|
@@ -879,7 +881,24 @@ function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTE
|
|
|
879
881
|
else if (n.item.lane === "actual") actual.push(seg);
|
|
880
882
|
else plan.push(seg);
|
|
881
883
|
}
|
|
882
|
-
|
|
884
|
+
const planPlaced = packTimed(plan, minMinutes, order);
|
|
885
|
+
const actualPlaced = packTimed(actual, minMinutes, order);
|
|
886
|
+
markSplit(planPlaced, actualPlaced, minMinutes);
|
|
887
|
+
markSplit(actualPlaced, planPlaced, minMinutes);
|
|
888
|
+
return { day, plan: planPlaced, actual: actualPlaced, background };
|
|
889
|
+
}
|
|
890
|
+
function markSplit(lane, other, minMinutes) {
|
|
891
|
+
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
|
+
const split = /* @__PURE__ */ new Set();
|
|
900
|
+
for (const [cluster, [s, e]] of ranges) if (other.some((o) => o.startMin < e && s < end(o))) split.add(cluster);
|
|
901
|
+
for (const p of lane) p.split = split.has(p.cluster);
|
|
883
902
|
}
|
|
884
903
|
function layoutRow(items, days, order = defaultOrder) {
|
|
885
904
|
const first = days[0];
|
|
@@ -1152,19 +1171,27 @@ var DayGridView = class {
|
|
|
1152
1171
|
});
|
|
1153
1172
|
}
|
|
1154
1173
|
w.pool.end();
|
|
1155
|
-
w.more.
|
|
1174
|
+
const existing = new Map(Array.from(w.more.children, (element) => [element.getAttribute("data-day"), element]));
|
|
1156
1175
|
hidden.forEach((items, col) => {
|
|
1157
1176
|
if (!items.length) return;
|
|
1158
1177
|
const day = w.days[col];
|
|
1159
|
-
const
|
|
1178
|
+
const key = String(day);
|
|
1179
|
+
let link = existing.get(key);
|
|
1180
|
+
if (!link) {
|
|
1181
|
+
link = h(ctx.doc, "button", "tnc-more", { type: "button", "aria-haspopup": "dialog", "data-day": key });
|
|
1182
|
+
const anchor = link;
|
|
1183
|
+
link.addEventListener("click", (ev) => ctx.openMore(day, anchor, ev));
|
|
1184
|
+
w.more.append(link);
|
|
1185
|
+
}
|
|
1186
|
+
existing.delete(key);
|
|
1187
|
+
link.textContent = ctx.text.more(items.length);
|
|
1160
1188
|
setStyle(link, {
|
|
1161
1189
|
left: `calc(${pct(col / n)} + 2px)`,
|
|
1162
1190
|
width: `calc(${pct(1 / n)} - 4px)`,
|
|
1163
1191
|
top: `calc(var(--tnc-dg-top-h) + ${visible(col)} * (var(--tnc-bar-h) + ${BAR_GAP}px))`
|
|
1164
1192
|
});
|
|
1165
|
-
link.addEventListener("click", (ev) => ctx.openMore(day, link, ev));
|
|
1166
|
-
w.more.append(link);
|
|
1167
1193
|
});
|
|
1194
|
+
for (const link of existing.values()) link.remove();
|
|
1168
1195
|
const step = this.barPx + BAR_GAP;
|
|
1169
1196
|
let need = maxLevel >= 0 ? (maxLevel + 1) * step - BAR_GAP : 0;
|
|
1170
1197
|
hidden.forEach((items, col) => {
|
|
@@ -1367,6 +1394,8 @@ var ListView = class {
|
|
|
1367
1394
|
if (!keep.size && !leadItems.length) {
|
|
1368
1395
|
if (!empty) {
|
|
1369
1396
|
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
1397
|
+
const content = ctx.opts.listEmptyContent?.();
|
|
1398
|
+
if (content) empty.replaceChildren(content);
|
|
1370
1399
|
this.body.append(empty);
|
|
1371
1400
|
}
|
|
1372
1401
|
} else empty?.remove();
|
|
@@ -1702,6 +1731,11 @@ var DateStrip = class {
|
|
|
1702
1731
|
|
|
1703
1732
|
// src/dom/views/timegrid.ts
|
|
1704
1733
|
var BAR_GAP2 = 2;
|
|
1734
|
+
function inLane(lane, split, left, width) {
|
|
1735
|
+
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)"];
|
|
1736
|
+
const f = (n) => Math.round(n * 1e4) / 1e4;
|
|
1737
|
+
return { left: `calc(${x} + (${w}) * ${f(left)})`, width: `calc((${w}) * ${f(width)} - 1px)` };
|
|
1738
|
+
}
|
|
1705
1739
|
var TimeGridView = class {
|
|
1706
1740
|
constructor(ctx) {
|
|
1707
1741
|
this.ctx = ctx;
|
|
@@ -1710,6 +1744,8 @@ var TimeGridView = class {
|
|
|
1710
1744
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1711
1745
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1712
1746
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1747
|
+
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1748
|
+
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
1713
1749
|
this.top = h(doc, "div", "tnc-tg-top");
|
|
1714
1750
|
this.headRow = h(doc, "div", "tnc-tg-head tnc-tg-cols", { role: "row" });
|
|
1715
1751
|
this.top.append(this.headRow);
|
|
@@ -1737,7 +1773,7 @@ var TimeGridView = class {
|
|
|
1737
1773
|
} else {
|
|
1738
1774
|
this.body = this.gutter = null;
|
|
1739
1775
|
}
|
|
1740
|
-
this.el.append(this.scroller);
|
|
1776
|
+
this.el.append(this.dayNavigation, this.scroller);
|
|
1741
1777
|
this.build();
|
|
1742
1778
|
if (showGrid && opts.height === "fill") {
|
|
1743
1779
|
const RO = ctx.win.ResizeObserver;
|
|
@@ -1750,6 +1786,7 @@ var TimeGridView = class {
|
|
|
1750
1786
|
ctx;
|
|
1751
1787
|
el;
|
|
1752
1788
|
scroller;
|
|
1789
|
+
dayNavigation;
|
|
1753
1790
|
top;
|
|
1754
1791
|
headRow;
|
|
1755
1792
|
allDayRow;
|
|
@@ -1805,6 +1842,9 @@ var TimeGridView = class {
|
|
|
1805
1842
|
head.append(cap);
|
|
1806
1843
|
}
|
|
1807
1844
|
this.headRow.append(head);
|
|
1845
|
+
const dayButton = h(doc, "button", "tnc-btn", { type: "button", "aria-label": ctx.fmt.longDate(day) }, `${ctx.fmt.weekdayShort(day)} ${ctx.fmt.dayNumber(day)}`);
|
|
1846
|
+
dayButton.addEventListener("click", () => this.revealDay(day));
|
|
1847
|
+
this.dayNavigation.append(dayButton);
|
|
1808
1848
|
opts.dayHeaderDidMount?.({ ...arg, el: head });
|
|
1809
1849
|
const col = h(doc, "div", ["tnc-col", ...ctx.dayClasses(day)].join(" "), { role: "cell", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr });
|
|
1810
1850
|
const bgLayer = h(doc, "div", "tnc-col-bg");
|
|
@@ -1821,6 +1861,7 @@ var TimeGridView = class {
|
|
|
1821
1861
|
this.body?.append(col);
|
|
1822
1862
|
return {
|
|
1823
1863
|
day,
|
|
1864
|
+
collisionColumns: 1,
|
|
1824
1865
|
head,
|
|
1825
1866
|
col,
|
|
1826
1867
|
plan: new ItemPool(ctx, planLayer),
|
|
@@ -1877,12 +1918,17 @@ var TimeGridView = class {
|
|
|
1877
1918
|
/* --------------------------------------------------------------- render */
|
|
1878
1919
|
render(change) {
|
|
1879
1920
|
this.renderAllDay();
|
|
1880
|
-
if (!this.body)
|
|
1921
|
+
if (!this.body) {
|
|
1922
|
+
this.updateDayNavigation();
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1881
1925
|
const dirty = change ? change.days : null;
|
|
1882
1926
|
for (const c of this.cols) {
|
|
1883
1927
|
if (dirty && !dirty.has(c.day) && !(this.ctx.slot.max > MINUTES_PER_DAY && dirty.has(c.day + 1))) continue;
|
|
1884
1928
|
this.renderColumn(c);
|
|
1885
1929
|
}
|
|
1930
|
+
this.el.style.setProperty("--tnc-collision-columns", String(Math.max(1, ...this.cols.map((column) => column.collisionColumns))));
|
|
1931
|
+
this.updateDayNavigation();
|
|
1886
1932
|
this.renderCapacity();
|
|
1887
1933
|
this.tick();
|
|
1888
1934
|
}
|
|
@@ -2000,6 +2046,11 @@ var TimeGridView = class {
|
|
|
2000
2046
|
}
|
|
2001
2047
|
const minMinutes = opts.eventMinHeight / this.ppm;
|
|
2002
2048
|
const layout = layoutTimedDay(items, c.day, zone, minMinutes, ctx.order, [slot.min, slot.max]);
|
|
2049
|
+
c.collisionColumns = Math.max(
|
|
2050
|
+
1,
|
|
2051
|
+
...layout.plan.map((placement) => opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols),
|
|
2052
|
+
...layout.actual.filter((placement) => !placement.split).map((placement) => placement.cols)
|
|
2053
|
+
);
|
|
2003
2054
|
const total = this.minutes;
|
|
2004
2055
|
const top = (m) => pct((m - slot.min) / total);
|
|
2005
2056
|
const height = (s) => pct((s.endMin - s.startMin) / total);
|
|
@@ -2011,19 +2062,28 @@ var TimeGridView = class {
|
|
|
2011
2062
|
c.bg.end();
|
|
2012
2063
|
const hasActual = layout.actual.length > 0;
|
|
2013
2064
|
c.col.classList.toggle("has-actual", hasActual);
|
|
2014
|
-
|
|
2015
|
-
this.placeLane(c.
|
|
2065
|
+
for (const link of c.more.children) link.setAttribute("data-stale", "true");
|
|
2066
|
+
this.placeLane(c.plan, layout.plan, c, top, height, "plan");
|
|
2067
|
+
this.placeLane(c.actual, layout.actual, c, top, height, "actual");
|
|
2068
|
+
for (const link of [...c.more.children]) if (link.hasAttribute("data-stale")) link.remove();
|
|
2016
2069
|
}
|
|
2017
|
-
placeLane(pool, placements, c, top, height,
|
|
2070
|
+
placeLane(pool, placements, c, top, height, lane) {
|
|
2018
2071
|
const { ctx } = this;
|
|
2019
2072
|
const { opts } = ctx;
|
|
2020
|
-
const
|
|
2073
|
+
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
2074
|
+
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
2075
|
+
const collapse = placements.some((placement) => {
|
|
2076
|
+
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
2077
|
+
return columns > 1 && columns * readableWidth > laneWidth;
|
|
2078
|
+
});
|
|
2079
|
+
const withMore = lane === "plan" || collapse;
|
|
2080
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2021
2081
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
2022
2082
|
pool.begin();
|
|
2023
2083
|
for (const p of placements) {
|
|
2024
2084
|
const preview = p.item.item.preview || p.item.id === ctx.draggingId;
|
|
2025
2085
|
if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
|
|
2026
|
-
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, items: [] };
|
|
2086
|
+
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
|
|
2027
2087
|
rec.count++;
|
|
2028
2088
|
rec.start = Math.min(rec.start, p.startMin);
|
|
2029
2089
|
rec.end = Math.max(rec.end, p.endMin);
|
|
@@ -2042,24 +2102,39 @@ var TimeGridView = class {
|
|
|
2042
2102
|
setStyle(el, {
|
|
2043
2103
|
top: top(p.startMin),
|
|
2044
2104
|
height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
2045
|
-
left
|
|
2046
|
-
width:
|
|
2105
|
+
...inLane(lane, p.split, left, width),
|
|
2106
|
+
"--tnc-event-readable-width": pct(span / cols / width),
|
|
2047
2107
|
"z-index": String(p.col + 1)
|
|
2048
2108
|
});
|
|
2049
2109
|
}
|
|
2050
2110
|
pool.end();
|
|
2051
2111
|
if (!withMore) return;
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2112
|
+
for (const [cluster, rec] of hiddenByCluster) {
|
|
2113
|
+
const key = `${lane}-${cluster}`;
|
|
2114
|
+
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
2115
|
+
if (!link) {
|
|
2116
|
+
link = h(ctx.doc, "button", "tnc-more tnc-more--stack", { type: "button", "aria-haspopup": "dialog", "data-cluster": key });
|
|
2117
|
+
const anchor = link;
|
|
2118
|
+
link.addEventListener("click", (ev) => ctx.openMore(c.day, anchor, ev, JSON.parse(anchor.dataset.itemIds ?? "[]")));
|
|
2119
|
+
c.more.append(link);
|
|
2120
|
+
}
|
|
2121
|
+
link.removeAttribute("data-stale");
|
|
2122
|
+
link.dataset.itemIds = JSON.stringify(placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.id));
|
|
2123
|
+
link.classList.toggle("tnc-more--collision", collapse);
|
|
2124
|
+
link.textContent = `+${rec.count}`;
|
|
2125
|
+
if (collapse) {
|
|
2126
|
+
link.textContent = ctx.text.appointments(rec.count);
|
|
2127
|
+
link.setAttribute("aria-label", link.textContent);
|
|
2128
|
+
link.title = link.getAttribute("aria-label");
|
|
2129
|
+
} else {
|
|
2130
|
+
link.removeAttribute("aria-label");
|
|
2131
|
+
link.removeAttribute("title");
|
|
2132
|
+
}
|
|
2055
2133
|
const cols = maxStack;
|
|
2056
2134
|
setStyle(link, {
|
|
2057
2135
|
top: top(rec.start),
|
|
2058
|
-
|
|
2059
|
-
width: `calc(${pct(1 / cols)} - 1px)`
|
|
2136
|
+
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
2060
2137
|
});
|
|
2061
|
-
link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
|
|
2062
|
-
c.more.append(link);
|
|
2063
2138
|
}
|
|
2064
2139
|
}
|
|
2065
2140
|
meta(s, bg) {
|
|
@@ -2170,6 +2245,26 @@ var TimeGridView = class {
|
|
|
2170
2245
|
}
|
|
2171
2246
|
resize() {
|
|
2172
2247
|
this.fitRows();
|
|
2248
|
+
this.render(null);
|
|
2249
|
+
}
|
|
2250
|
+
revealDay(day) {
|
|
2251
|
+
const column = this.cols.find((candidate) => candidate.day === day);
|
|
2252
|
+
if (!column) return;
|
|
2253
|
+
const bounds = column.head.getBoundingClientRect();
|
|
2254
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2255
|
+
this.scroller.scrollLeft += bounds.left - viewport.left - (viewport.width - bounds.width) / 2;
|
|
2256
|
+
this.updateDayNavigation();
|
|
2257
|
+
}
|
|
2258
|
+
updateDayNavigation() {
|
|
2259
|
+
this.dayNavigation.hidden = this.scroller.scrollWidth <= this.scroller.clientWidth + 1;
|
|
2260
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2261
|
+
this.cols.forEach((column, index) => {
|
|
2262
|
+
const bounds = column.head.getBoundingClientRect();
|
|
2263
|
+
const visible = bounds.left >= viewport.left && bounds.right <= viewport.right;
|
|
2264
|
+
const button = this.dayNavigation.children[index];
|
|
2265
|
+
button?.classList.toggle("is-offscreen", !visible);
|
|
2266
|
+
button?.setAttribute("aria-pressed", String(visible));
|
|
2267
|
+
});
|
|
2173
2268
|
}
|
|
2174
2269
|
scrollToTime(minute) {
|
|
2175
2270
|
if (!this.body) return;
|
|
@@ -2842,6 +2937,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2842
2937
|
|
|
2843
2938
|
// src/dom/calendar.ts
|
|
2844
2939
|
var EXTERNAL_ID = "__tnc_external__";
|
|
2940
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
2845
2941
|
var MAX_AGENDA_DAYS = 366;
|
|
2846
2942
|
function sameOption(a, b, depth = 0) {
|
|
2847
2943
|
if (a === b) return true;
|
|
@@ -2934,7 +3030,11 @@ var Calendar = class {
|
|
|
2934
3030
|
this.resizeObs?.disconnect();
|
|
2935
3031
|
const RO = this.win.ResizeObserver;
|
|
2936
3032
|
if (RO) {
|
|
2937
|
-
this.resizeObs = new RO(() =>
|
|
3033
|
+
this.resizeObs = new RO(() => {
|
|
3034
|
+
this.syncToolbarViewDisplay();
|
|
3035
|
+
this.current?.resize?.();
|
|
3036
|
+
this.positionMore();
|
|
3037
|
+
});
|
|
2938
3038
|
this.resizeObs.observe(this.root);
|
|
2939
3039
|
}
|
|
2940
3040
|
}
|
|
@@ -2954,9 +3054,11 @@ var Calendar = class {
|
|
|
2954
3054
|
if (this.frame !== null) old.cancelAnimationFrame(this.frame);
|
|
2955
3055
|
this.timer = this.frame = null;
|
|
2956
3056
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3057
|
+
this.listenMoreGeometry(false);
|
|
2957
3058
|
this.doc = doc;
|
|
2958
3059
|
this.win = doc.defaultView ?? old;
|
|
2959
3060
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3061
|
+
this.listenMoreGeometry(true);
|
|
2960
3062
|
this.observeResize();
|
|
2961
3063
|
this.rebuild(false);
|
|
2962
3064
|
this.scheduleTick();
|
|
@@ -3074,6 +3176,7 @@ var Calendar = class {
|
|
|
3074
3176
|
goToday() {
|
|
3075
3177
|
this.anchor = this.todayDay();
|
|
3076
3178
|
this.rebuild(true);
|
|
3179
|
+
if (this.current instanceof TimeGridView) this.current.revealDay(this.anchor);
|
|
3077
3180
|
}
|
|
3078
3181
|
/** The anchor date, as local midnight of that civil day in the calendar zone. */
|
|
3079
3182
|
getDate() {
|
|
@@ -3092,6 +3195,7 @@ var Calendar = class {
|
|
|
3092
3195
|
/** Re-measures (e.g. after the host was shown or resized by a parent). */
|
|
3093
3196
|
updateSize() {
|
|
3094
3197
|
this.current?.resize?.();
|
|
3198
|
+
this.positionMore();
|
|
3095
3199
|
}
|
|
3096
3200
|
getScroll() {
|
|
3097
3201
|
return this.current?.getScroll() ?? { top: 0, left: 0 };
|
|
@@ -3123,7 +3227,10 @@ var Calendar = class {
|
|
|
3123
3227
|
}
|
|
3124
3228
|
const view = this.current;
|
|
3125
3229
|
if (view instanceof TimelineView) view.scrollToInstant(ms, opts.align ?? "center", opts.smooth ?? false);
|
|
3126
|
-
else if (view instanceof TimeGridView
|
|
3230
|
+
else if (view instanceof TimeGridView) {
|
|
3231
|
+
view.revealDay(day);
|
|
3232
|
+
if (p.kind !== "day") view.scrollToTime(wallTime(ms, this.zone).minute);
|
|
3233
|
+
}
|
|
3127
3234
|
}
|
|
3128
3235
|
/** What is under a client point: a time slot, a day, or a timeline row and date. Null outside the view. */
|
|
3129
3236
|
hitTest(clientX, clientY) {
|
|
@@ -3298,9 +3405,10 @@ var Calendar = class {
|
|
|
3298
3405
|
if (meta) this.opts.itemWillUnmount?.(this.itemArg(el, meta));
|
|
3299
3406
|
el.remove();
|
|
3300
3407
|
}
|
|
3301
|
-
openMore(day, anchor, jsEvent) {
|
|
3408
|
+
openMore(day, anchor, jsEvent, itemIds) {
|
|
3302
3409
|
jsEvent.stopPropagation();
|
|
3303
|
-
const
|
|
3410
|
+
const group = itemIds ? new Set(itemIds) : null;
|
|
3411
|
+
const items = this.store.inRange(day, day + 1).filter((item) => !group || group.has(item.id)).filter((n) => n.item.display !== "background" && (n.allDay || anchor.classList.contains("tnc-more--stack") || this.opts.view === "month" || !this.opts.timeGrid || this.opts.eventMaxStack > 0)).sort((a, b) => Number(b.allDay) - Number(a.allDay) || this.order(a, b));
|
|
3304
3412
|
const shown = new Set(
|
|
3305
3413
|
[...this.viewHost.querySelectorAll(".tnc-item")].filter((el2) => this.meta.get(el2)?.day === day || (this.meta.get(el2)?.n.startDay ?? Infinity) <= day).map((el2) => el2.dataset.id ?? "")
|
|
3306
3414
|
);
|
|
@@ -3329,15 +3437,7 @@ var Calendar = class {
|
|
|
3329
3437
|
}
|
|
3330
3438
|
pool.end();
|
|
3331
3439
|
this.popover = { el, pool, anchor, day };
|
|
3332
|
-
|
|
3333
|
-
const ar = anchor.getBoundingClientRect();
|
|
3334
|
-
const w = Math.max(220, ar.width + 24);
|
|
3335
|
-
let left = ar.left - rr.left - 12;
|
|
3336
|
-
left = Math.max(4, Math.min(left, rr.width - w - 4));
|
|
3337
|
-
let top = ar.top - rr.top - 40;
|
|
3338
|
-
const height = el.offsetHeight;
|
|
3339
|
-
top = Math.max(4, Math.min(top, rr.height - height - 4));
|
|
3340
|
-
setStyle(el, { left, top, width: w });
|
|
3440
|
+
this.positionMore();
|
|
3341
3441
|
el.focus();
|
|
3342
3442
|
}
|
|
3343
3443
|
/* =============================================================== render */
|
|
@@ -3461,8 +3561,10 @@ var Calendar = class {
|
|
|
3461
3561
|
this.current.setScroll({ top: keep.top, left: 0 });
|
|
3462
3562
|
this.current.placeInstant(keep.ms, keep.vx);
|
|
3463
3563
|
} else if (scroll) this.current.setScroll(scroll);
|
|
3464
|
-
else if (this.current instanceof TimeGridView)
|
|
3465
|
-
|
|
3564
|
+
else if (this.current instanceof TimeGridView) {
|
|
3565
|
+
this.current.scrollToTime(this.slot.scroll);
|
|
3566
|
+
this.current.revealDay(this.anchor);
|
|
3567
|
+
} else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
3466
3568
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
3467
3569
|
}
|
|
3468
3570
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -3610,28 +3712,65 @@ var Calendar = class {
|
|
|
3610
3712
|
if (e.icon) b.replaceChildren(e.icon());
|
|
3611
3713
|
return b;
|
|
3612
3714
|
});
|
|
3715
|
+
const chevron = (d) => {
|
|
3716
|
+
const svg = this.doc.createElementNS(SVG_NS, "svg");
|
|
3717
|
+
setAttrs(svg, { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false" });
|
|
3718
|
+
const path = this.doc.createElementNS(SVG_NS, "path");
|
|
3719
|
+
path.setAttribute("d", d);
|
|
3720
|
+
svg.append(path);
|
|
3721
|
+
return svg;
|
|
3722
|
+
};
|
|
3723
|
+
const prev = btn("", this.text.prev, "tnc-btn-prev", () => this.prev());
|
|
3724
|
+
prev.append(chevron("m15 18-6-6 6-6"));
|
|
3725
|
+
const next = btn("", this.text.next, "tnc-btn-next", () => this.next());
|
|
3726
|
+
next.append(chevron("m9 18 6-6-6-6"));
|
|
3613
3727
|
const nav = h(
|
|
3614
3728
|
this.doc,
|
|
3615
3729
|
"div",
|
|
3616
3730
|
"tnc-toolbar-group tnc-toolbar-nav",
|
|
3617
3731
|
null,
|
|
3618
|
-
|
|
3732
|
+
prev,
|
|
3619
3733
|
btn(this.text.today, this.text.today, "tnc-btn-today", () => this.goToday()),
|
|
3620
|
-
|
|
3734
|
+
next
|
|
3621
3735
|
);
|
|
3622
3736
|
const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
|
|
3623
3737
|
const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
|
|
3738
|
+
const viewSelect = h(this.doc, "select", "tnc-btn tnc-toolbar-view-select", {
|
|
3739
|
+
"aria-label": this.text.viewHints[this.opts.view] ?? this.text.views[this.opts.view] ?? this.opts.view
|
|
3740
|
+
});
|
|
3624
3741
|
for (const v of views) {
|
|
3625
3742
|
const label = v === "days" ? this.text.views.days ?? `${this.opts.dayCount} days` : this.text.views[v] ?? v;
|
|
3626
3743
|
viewGroup.append(btn(label, this.text.viewHints[v] ?? label, `tnc-btn-view tnc-btn-view-${v}`, () => this.changeView(v), v === this.opts.view));
|
|
3744
|
+
viewSelect.append(h(this.doc, "option", "", { value: v }, label));
|
|
3627
3745
|
}
|
|
3746
|
+
viewSelect.value = this.opts.view;
|
|
3747
|
+
viewSelect.addEventListener("change", () => this.changeView(viewSelect.value));
|
|
3628
3748
|
const start = h(this.doc, "div", "tnc-toolbar-start");
|
|
3629
3749
|
const end = h(this.doc, "div", "tnc-toolbar-end");
|
|
3630
3750
|
if (cfg.extraPlacement === "start") start.append(...extras);
|
|
3631
3751
|
start.append(nav, title);
|
|
3632
|
-
end.append(viewGroup);
|
|
3752
|
+
end.append(viewGroup, viewSelect);
|
|
3633
3753
|
if (cfg.extraPlacement !== "start") end.append(...extras);
|
|
3634
3754
|
this.toolbarEl.append(start, end);
|
|
3755
|
+
this.syncToolbarViewDisplay();
|
|
3756
|
+
}
|
|
3757
|
+
syncToolbarViewDisplay() {
|
|
3758
|
+
const buttons = this.toolbarEl.querySelector(".tnc-toolbar-views");
|
|
3759
|
+
const select = this.toolbarEl.querySelector(".tnc-toolbar-view-select");
|
|
3760
|
+
const start = this.toolbarEl.querySelector(".tnc-toolbar-start");
|
|
3761
|
+
if (!buttons || !select || !start) return;
|
|
3762
|
+
const config = this.opts.toolbar;
|
|
3763
|
+
const mode = typeof config === "object" ? config.viewDisplay ?? "auto" : "auto";
|
|
3764
|
+
buttons.hidden = false;
|
|
3765
|
+
select.hidden = true;
|
|
3766
|
+
buttons.style.display = "flex";
|
|
3767
|
+
select.style.display = "none";
|
|
3768
|
+
const startWidth = Array.from(start.children).reduce((width, child) => width + child.scrollWidth, 0);
|
|
3769
|
+
const compact = mode === "select" || mode === "auto" && startWidth + buttons.scrollWidth + 32 > this.root.clientWidth;
|
|
3770
|
+
buttons.hidden = compact;
|
|
3771
|
+
select.hidden = !compact;
|
|
3772
|
+
buttons.style.display = compact ? "none" : "flex";
|
|
3773
|
+
select.style.display = compact ? "" : "none";
|
|
3635
3774
|
}
|
|
3636
3775
|
applyChange(change) {
|
|
3637
3776
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3764,6 +3903,38 @@ var Calendar = class {
|
|
|
3764
3903
|
}
|
|
3765
3904
|
}
|
|
3766
3905
|
/* ============================================================== popover */
|
|
3906
|
+
onMoreGeometryChange = () => this.positionMore();
|
|
3907
|
+
listenMoreGeometry(on) {
|
|
3908
|
+
const method = on ? "addEventListener" : "removeEventListener";
|
|
3909
|
+
this.doc[method]("scroll", this.onMoreGeometryChange, true);
|
|
3910
|
+
this.win[method]("resize", this.onMoreGeometryChange);
|
|
3911
|
+
this.win.visualViewport?.[method]("resize", this.onMoreGeometryChange);
|
|
3912
|
+
this.win.visualViewport?.[method]("scroll", this.onMoreGeometryChange);
|
|
3913
|
+
}
|
|
3914
|
+
positionMore() {
|
|
3915
|
+
const popover = this.popover;
|
|
3916
|
+
if (!popover) return;
|
|
3917
|
+
if (!popover.anchor.isConnected) {
|
|
3918
|
+
this.closeMore();
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
const viewport = this.win.visualViewport;
|
|
3922
|
+
const root = this.root.getBoundingClientRect();
|
|
3923
|
+
const anchor = popover.anchor.getBoundingClientRect();
|
|
3924
|
+
const leftEdge = Math.max(root.left, viewport?.offsetLeft ?? 0) + 4;
|
|
3925
|
+
const topEdge = Math.max(root.top, viewport?.offsetTop ?? 0) + 4;
|
|
3926
|
+
const rightEdge = Math.min(root.right, (viewport?.offsetLeft ?? 0) + (viewport?.width ?? this.win.innerWidth)) - 4;
|
|
3927
|
+
const bottomEdge = Math.min(root.bottom, (viewport?.offsetTop ?? 0) + (viewport?.height ?? this.win.innerHeight)) - 4;
|
|
3928
|
+
if (rightEdge <= leftEdge || bottomEdge <= topEdge) {
|
|
3929
|
+
this.closeMore();
|
|
3930
|
+
return;
|
|
3931
|
+
}
|
|
3932
|
+
const width = Math.min(360, rightEdge - leftEdge);
|
|
3933
|
+
setStyle(popover.el, { width, "max-height": Math.min(420, bottomEdge - topEdge) });
|
|
3934
|
+
const left = Math.max(leftEdge, Math.min(anchor.left - 12, rightEdge - width));
|
|
3935
|
+
const top = Math.max(topEdge, Math.min(anchor.top - 40, bottomEdge - popover.el.offsetHeight));
|
|
3936
|
+
setStyle(popover.el, { left: left - root.left, top: top - root.top });
|
|
3937
|
+
}
|
|
3767
3938
|
refreshMore() {
|
|
3768
3939
|
const p = this.popover;
|
|
3769
3940
|
if (!p) return;
|
|
@@ -3773,6 +3944,7 @@ var Calendar = class {
|
|
|
3773
3944
|
p.pool.place(n.id, { n, day: p.day, isStart: n.startDay >= p.day, isEnd: n.endDay <= p.day + 1, variant: n.allDay ? "bar" : "dot", mirror: false });
|
|
3774
3945
|
}
|
|
3775
3946
|
p.pool.end();
|
|
3947
|
+
this.positionMore();
|
|
3776
3948
|
}
|
|
3777
3949
|
closeMore(restoreFocus = false) {
|
|
3778
3950
|
const p = this.popover;
|
|
@@ -3800,6 +3972,7 @@ var Calendar = class {
|
|
|
3800
3972
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
3801
3973
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
3802
3974
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3975
|
+
this.listenMoreGeometry(true);
|
|
3803
3976
|
}
|
|
3804
3977
|
unbindEvents() {
|
|
3805
3978
|
const r = this.root;
|
|
@@ -3818,6 +3991,7 @@ var Calendar = class {
|
|
|
3818
3991
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
3819
3992
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
3820
3993
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3994
|
+
this.listenMoreGeometry(false);
|
|
3821
3995
|
this.listenDoc(false);
|
|
3822
3996
|
}
|
|
3823
3997
|
listenDoc(on) {
|
|
@@ -3863,6 +4037,7 @@ var Calendar = class {
|
|
|
3863
4037
|
mode = "select";
|
|
3864
4038
|
}
|
|
3865
4039
|
const touch = ev.pointerType === "touch";
|
|
4040
|
+
const overflowOrigin = itemEl?.closest(".tnc-popover") && origin ? wallTime(origin.startMs, this.zone) : null;
|
|
3866
4041
|
const s = {
|
|
3867
4042
|
mode,
|
|
3868
4043
|
pointerId: ev.pointerId,
|
|
@@ -3871,7 +4046,7 @@ var Calendar = class {
|
|
|
3871
4046
|
y0: ev.clientY,
|
|
3872
4047
|
active: false,
|
|
3873
4048
|
origin,
|
|
3874
|
-
grab: this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
4049
|
+
grab: overflowOrigin && this.current instanceof TimeGridView && !origin?.allDay ? { kind: "time", day: overflowOrigin.day, minute: overflowOrigin.minute } : this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
3875
4050
|
proposal: null,
|
|
3876
4051
|
denied: false,
|
|
3877
4052
|
timer: null,
|
|
@@ -4374,6 +4549,25 @@ var Calendar = class {
|
|
|
4374
4549
|
if (el && meta) this.opts.onItemHover?.({ ...this.itemArg(el, meta), jsEvent: ev, entering: false });
|
|
4375
4550
|
}
|
|
4376
4551
|
onKeyDown = (ev) => {
|
|
4552
|
+
if (this.popover && !ev.altKey && !ev.ctrlKey && !ev.metaKey && ["ArrowDown", "ArrowUp", "Home", "End"].includes(ev.key)) {
|
|
4553
|
+
const rows = [...this.popover.el.querySelectorAll(".tnc-item")];
|
|
4554
|
+
const index = rows.indexOf(this.doc.activeElement);
|
|
4555
|
+
const next = ev.key === "Home" ? 0 : ev.key === "End" ? rows.length - 1 : ev.key === "ArrowDown" ? (index + 1) % rows.length : index <= 0 ? rows.length - 1 : index - 1;
|
|
4556
|
+
ev.preventDefault();
|
|
4557
|
+
ev.stopPropagation();
|
|
4558
|
+
rows[next]?.focus();
|
|
4559
|
+
rows[next]?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
4560
|
+
return;
|
|
4561
|
+
}
|
|
4562
|
+
if (ev.key === "Tab" && this.popover) {
|
|
4563
|
+
const controls = [...this.popover.el.querySelectorAll('button, a[href], [tabindex="0"]')];
|
|
4564
|
+
const index = controls.indexOf(this.doc.activeElement);
|
|
4565
|
+
const next = ev.shiftKey ? index <= 0 ? controls.length - 1 : index - 1 : (index + 1) % controls.length;
|
|
4566
|
+
ev.preventDefault();
|
|
4567
|
+
ev.stopPropagation();
|
|
4568
|
+
controls[next]?.focus();
|
|
4569
|
+
return;
|
|
4570
|
+
}
|
|
4377
4571
|
if (ev.key === "Escape" && this.popover) {
|
|
4378
4572
|
ev.preventDefault();
|
|
4379
4573
|
ev.stopPropagation();
|
|
@@ -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
|
-
|
|
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];
|