@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/index.cjs
CHANGED
|
@@ -532,7 +532,7 @@ function packTimed(segs, minMinutes, order = defaultOrder) {
|
|
|
532
532
|
colEnds.push(0);
|
|
533
533
|
}
|
|
534
534
|
colEnds[col] = s.end;
|
|
535
|
-
const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex };
|
|
535
|
+
const p = { ...s, col, cols: 1, span: 1, cluster: clusterIndex, split: false };
|
|
536
536
|
cluster.push(p);
|
|
537
537
|
out.push(p);
|
|
538
538
|
clusterEnd = Math.max(clusterEnd, s.end);
|
|
@@ -552,7 +552,24 @@ function layoutTimedDay(items, day, zone, minMinutes, order, window = [0, MINUTE
|
|
|
552
552
|
else if (n.item.lane === "actual") actual.push(seg);
|
|
553
553
|
else plan.push(seg);
|
|
554
554
|
}
|
|
555
|
-
|
|
555
|
+
const planPlaced = packTimed(plan, minMinutes, order);
|
|
556
|
+
const actualPlaced = packTimed(actual, minMinutes, order);
|
|
557
|
+
markSplit(planPlaced, actualPlaced, minMinutes);
|
|
558
|
+
markSplit(actualPlaced, planPlaced, minMinutes);
|
|
559
|
+
return { day, plan: planPlaced, actual: actualPlaced, background };
|
|
560
|
+
}
|
|
561
|
+
function markSplit(lane, other, minMinutes) {
|
|
562
|
+
if (!lane.length || !other.length) return;
|
|
563
|
+
const end = (p) => Math.max(p.endMin, p.startMin + minMinutes);
|
|
564
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
565
|
+
for (const p of lane) {
|
|
566
|
+
const r = ranges.get(p.cluster);
|
|
567
|
+
if (r) r[1] = Math.max(r[1], end(p));
|
|
568
|
+
else ranges.set(p.cluster, [p.startMin, end(p)]);
|
|
569
|
+
}
|
|
570
|
+
const split = /* @__PURE__ */ new Set();
|
|
571
|
+
for (const [cluster, [s, e]] of ranges) if (other.some((o) => o.startMin < e && s < end(o))) split.add(cluster);
|
|
572
|
+
for (const p of lane) p.split = split.has(p.cluster);
|
|
556
573
|
}
|
|
557
574
|
function layoutRow(items, days, order = defaultOrder) {
|
|
558
575
|
const first = days[0];
|
|
@@ -936,6 +953,7 @@ var DEFAULT_TEXT = {
|
|
|
936
953
|
allDay: "All day",
|
|
937
954
|
noItems: "Nothing scheduled",
|
|
938
955
|
more: (n) => `+${n} more`,
|
|
956
|
+
appointments: (n) => `${n} appointments`,
|
|
939
957
|
itemCount: (n) => n === 1 ? "1 item" : `${n} items`,
|
|
940
958
|
weekNumber: (n) => `W${n}`,
|
|
941
959
|
views: { week: "Week", day: "Day", days: "Days", month: "Month", list: "List", year: "Year", timeline: "Timeline" },
|
|
@@ -1005,6 +1023,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
1005
1023
|
"dateStrip",
|
|
1006
1024
|
"listLeadTitle",
|
|
1007
1025
|
"listAddText",
|
|
1026
|
+
"listEmptyContent",
|
|
1008
1027
|
"monthDisplay",
|
|
1009
1028
|
"timeZone",
|
|
1010
1029
|
"locale",
|
|
@@ -1244,19 +1263,27 @@ var DayGridView = class {
|
|
|
1244
1263
|
});
|
|
1245
1264
|
}
|
|
1246
1265
|
w.pool.end();
|
|
1247
|
-
w.more.
|
|
1266
|
+
const existing = new Map(Array.from(w.more.children, (element) => [element.getAttribute("data-day"), element]));
|
|
1248
1267
|
hidden.forEach((items, col) => {
|
|
1249
1268
|
if (!items.length) return;
|
|
1250
1269
|
const day = w.days[col];
|
|
1251
|
-
const
|
|
1270
|
+
const key = String(day);
|
|
1271
|
+
let link = existing.get(key);
|
|
1272
|
+
if (!link) {
|
|
1273
|
+
link = h(ctx.doc, "button", "tnc-more", { type: "button", "aria-haspopup": "dialog", "data-day": key });
|
|
1274
|
+
const anchor = link;
|
|
1275
|
+
link.addEventListener("click", (ev) => ctx.openMore(day, anchor, ev));
|
|
1276
|
+
w.more.append(link);
|
|
1277
|
+
}
|
|
1278
|
+
existing.delete(key);
|
|
1279
|
+
link.textContent = ctx.text.more(items.length);
|
|
1252
1280
|
setStyle(link, {
|
|
1253
1281
|
left: `calc(${pct(col / n)} + 2px)`,
|
|
1254
1282
|
width: `calc(${pct(1 / n)} - 4px)`,
|
|
1255
1283
|
top: `calc(var(--tnc-dg-top-h) + ${visible(col)} * (var(--tnc-bar-h) + ${BAR_GAP}px))`
|
|
1256
1284
|
});
|
|
1257
|
-
link.addEventListener("click", (ev) => ctx.openMore(day, link, ev));
|
|
1258
|
-
w.more.append(link);
|
|
1259
1285
|
});
|
|
1286
|
+
for (const link of existing.values()) link.remove();
|
|
1260
1287
|
const step = this.barPx + BAR_GAP;
|
|
1261
1288
|
let need = maxLevel >= 0 ? (maxLevel + 1) * step - BAR_GAP : 0;
|
|
1262
1289
|
hidden.forEach((items, col) => {
|
|
@@ -1459,6 +1486,8 @@ var ListView = class {
|
|
|
1459
1486
|
if (!keep.size && !leadItems.length) {
|
|
1460
1487
|
if (!empty) {
|
|
1461
1488
|
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
1489
|
+
const content = ctx.opts.listEmptyContent?.();
|
|
1490
|
+
if (content) empty.replaceChildren(content);
|
|
1462
1491
|
this.body.append(empty);
|
|
1463
1492
|
}
|
|
1464
1493
|
} else empty?.remove();
|
|
@@ -1794,6 +1823,11 @@ var DateStrip = class {
|
|
|
1794
1823
|
|
|
1795
1824
|
// src/dom/views/timegrid.ts
|
|
1796
1825
|
var BAR_GAP2 = 2;
|
|
1826
|
+
function inLane(lane, split, left, width) {
|
|
1827
|
+
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)"];
|
|
1828
|
+
const f = (n) => Math.round(n * 1e4) / 1e4;
|
|
1829
|
+
return { left: `calc(${x} + (${w}) * ${f(left)})`, width: `calc((${w}) * ${f(width)} - 1px)` };
|
|
1830
|
+
}
|
|
1797
1831
|
var TimeGridView = class {
|
|
1798
1832
|
constructor(ctx) {
|
|
1799
1833
|
this.ctx = ctx;
|
|
@@ -1802,6 +1836,8 @@ var TimeGridView = class {
|
|
|
1802
1836
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1803
1837
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1804
1838
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1839
|
+
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1840
|
+
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
1805
1841
|
this.top = h(doc, "div", "tnc-tg-top");
|
|
1806
1842
|
this.headRow = h(doc, "div", "tnc-tg-head tnc-tg-cols", { role: "row" });
|
|
1807
1843
|
this.top.append(this.headRow);
|
|
@@ -1829,7 +1865,7 @@ var TimeGridView = class {
|
|
|
1829
1865
|
} else {
|
|
1830
1866
|
this.body = this.gutter = null;
|
|
1831
1867
|
}
|
|
1832
|
-
this.el.append(this.scroller);
|
|
1868
|
+
this.el.append(this.dayNavigation, this.scroller);
|
|
1833
1869
|
this.build();
|
|
1834
1870
|
if (showGrid && opts.height === "fill") {
|
|
1835
1871
|
const RO = ctx.win.ResizeObserver;
|
|
@@ -1842,6 +1878,7 @@ var TimeGridView = class {
|
|
|
1842
1878
|
ctx;
|
|
1843
1879
|
el;
|
|
1844
1880
|
scroller;
|
|
1881
|
+
dayNavigation;
|
|
1845
1882
|
top;
|
|
1846
1883
|
headRow;
|
|
1847
1884
|
allDayRow;
|
|
@@ -1897,6 +1934,9 @@ var TimeGridView = class {
|
|
|
1897
1934
|
head.append(cap);
|
|
1898
1935
|
}
|
|
1899
1936
|
this.headRow.append(head);
|
|
1937
|
+
const dayButton = h(doc, "button", "tnc-btn", { type: "button", "aria-label": ctx.fmt.longDate(day) }, `${ctx.fmt.weekdayShort(day)} ${ctx.fmt.dayNumber(day)}`);
|
|
1938
|
+
dayButton.addEventListener("click", () => this.revealDay(day));
|
|
1939
|
+
this.dayNavigation.append(dayButton);
|
|
1900
1940
|
opts.dayHeaderDidMount?.({ ...arg, el: head });
|
|
1901
1941
|
const col = h(doc, "div", ["tnc-col", ...ctx.dayClasses(day)].join(" "), { role: "cell", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr });
|
|
1902
1942
|
const bgLayer = h(doc, "div", "tnc-col-bg");
|
|
@@ -1913,6 +1953,7 @@ var TimeGridView = class {
|
|
|
1913
1953
|
this.body?.append(col);
|
|
1914
1954
|
return {
|
|
1915
1955
|
day,
|
|
1956
|
+
collisionColumns: 1,
|
|
1916
1957
|
head,
|
|
1917
1958
|
col,
|
|
1918
1959
|
plan: new ItemPool(ctx, planLayer),
|
|
@@ -1969,12 +2010,17 @@ var TimeGridView = class {
|
|
|
1969
2010
|
/* --------------------------------------------------------------- render */
|
|
1970
2011
|
render(change) {
|
|
1971
2012
|
this.renderAllDay();
|
|
1972
|
-
if (!this.body)
|
|
2013
|
+
if (!this.body) {
|
|
2014
|
+
this.updateDayNavigation();
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
1973
2017
|
const dirty = change ? change.days : null;
|
|
1974
2018
|
for (const c of this.cols) {
|
|
1975
2019
|
if (dirty && !dirty.has(c.day) && !(this.ctx.slot.max > MINUTES_PER_DAY && dirty.has(c.day + 1))) continue;
|
|
1976
2020
|
this.renderColumn(c);
|
|
1977
2021
|
}
|
|
2022
|
+
this.el.style.setProperty("--tnc-collision-columns", String(Math.max(1, ...this.cols.map((column) => column.collisionColumns))));
|
|
2023
|
+
this.updateDayNavigation();
|
|
1978
2024
|
this.renderCapacity();
|
|
1979
2025
|
this.tick();
|
|
1980
2026
|
}
|
|
@@ -2092,6 +2138,11 @@ var TimeGridView = class {
|
|
|
2092
2138
|
}
|
|
2093
2139
|
const minMinutes = opts.eventMinHeight / this.ppm;
|
|
2094
2140
|
const layout = layoutTimedDay(items, c.day, zone, minMinutes, ctx.order, [slot.min, slot.max]);
|
|
2141
|
+
c.collisionColumns = Math.max(
|
|
2142
|
+
1,
|
|
2143
|
+
...layout.plan.map((placement) => opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols),
|
|
2144
|
+
...layout.actual.filter((placement) => !placement.split).map((placement) => placement.cols)
|
|
2145
|
+
);
|
|
2095
2146
|
const total = this.minutes;
|
|
2096
2147
|
const top = (m) => pct((m - slot.min) / total);
|
|
2097
2148
|
const height = (s) => pct((s.endMin - s.startMin) / total);
|
|
@@ -2103,19 +2154,28 @@ var TimeGridView = class {
|
|
|
2103
2154
|
c.bg.end();
|
|
2104
2155
|
const hasActual = layout.actual.length > 0;
|
|
2105
2156
|
c.col.classList.toggle("has-actual", hasActual);
|
|
2106
|
-
|
|
2107
|
-
this.placeLane(c.
|
|
2157
|
+
for (const link of c.more.children) link.setAttribute("data-stale", "true");
|
|
2158
|
+
this.placeLane(c.plan, layout.plan, c, top, height, "plan");
|
|
2159
|
+
this.placeLane(c.actual, layout.actual, c, top, height, "actual");
|
|
2160
|
+
for (const link of [...c.more.children]) if (link.hasAttribute("data-stale")) link.remove();
|
|
2108
2161
|
}
|
|
2109
|
-
placeLane(pool, placements, c, top, height,
|
|
2162
|
+
placeLane(pool, placements, c, top, height, lane) {
|
|
2110
2163
|
const { ctx } = this;
|
|
2111
2164
|
const { opts } = ctx;
|
|
2112
|
-
const
|
|
2165
|
+
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
2166
|
+
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
2167
|
+
const collapse = placements.some((placement) => {
|
|
2168
|
+
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
2169
|
+
return columns > 1 && columns * readableWidth > laneWidth;
|
|
2170
|
+
});
|
|
2171
|
+
const withMore = lane === "plan" || collapse;
|
|
2172
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2113
2173
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
2114
2174
|
pool.begin();
|
|
2115
2175
|
for (const p of placements) {
|
|
2116
2176
|
const preview = p.item.item.preview || p.item.id === ctx.draggingId;
|
|
2117
2177
|
if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
|
|
2118
|
-
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, items: [] };
|
|
2178
|
+
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
|
|
2119
2179
|
rec.count++;
|
|
2120
2180
|
rec.start = Math.min(rec.start, p.startMin);
|
|
2121
2181
|
rec.end = Math.max(rec.end, p.endMin);
|
|
@@ -2134,24 +2194,39 @@ var TimeGridView = class {
|
|
|
2134
2194
|
setStyle(el, {
|
|
2135
2195
|
top: top(p.startMin),
|
|
2136
2196
|
height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
2137
|
-
left
|
|
2138
|
-
width:
|
|
2197
|
+
...inLane(lane, p.split, left, width),
|
|
2198
|
+
"--tnc-event-readable-width": pct(span / cols / width),
|
|
2139
2199
|
"z-index": String(p.col + 1)
|
|
2140
2200
|
});
|
|
2141
2201
|
}
|
|
2142
2202
|
pool.end();
|
|
2143
2203
|
if (!withMore) return;
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2204
|
+
for (const [cluster, rec] of hiddenByCluster) {
|
|
2205
|
+
const key = `${lane}-${cluster}`;
|
|
2206
|
+
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
2207
|
+
if (!link) {
|
|
2208
|
+
link = h(ctx.doc, "button", "tnc-more tnc-more--stack", { type: "button", "aria-haspopup": "dialog", "data-cluster": key });
|
|
2209
|
+
const anchor = link;
|
|
2210
|
+
link.addEventListener("click", (ev) => ctx.openMore(c.day, anchor, ev, JSON.parse(anchor.dataset.itemIds ?? "[]")));
|
|
2211
|
+
c.more.append(link);
|
|
2212
|
+
}
|
|
2213
|
+
link.removeAttribute("data-stale");
|
|
2214
|
+
link.dataset.itemIds = JSON.stringify(placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.id));
|
|
2215
|
+
link.classList.toggle("tnc-more--collision", collapse);
|
|
2216
|
+
link.textContent = `+${rec.count}`;
|
|
2217
|
+
if (collapse) {
|
|
2218
|
+
link.textContent = ctx.text.appointments(rec.count);
|
|
2219
|
+
link.setAttribute("aria-label", link.textContent);
|
|
2220
|
+
link.title = link.getAttribute("aria-label");
|
|
2221
|
+
} else {
|
|
2222
|
+
link.removeAttribute("aria-label");
|
|
2223
|
+
link.removeAttribute("title");
|
|
2224
|
+
}
|
|
2147
2225
|
const cols = maxStack;
|
|
2148
2226
|
setStyle(link, {
|
|
2149
2227
|
top: top(rec.start),
|
|
2150
|
-
|
|
2151
|
-
width: `calc(${pct(1 / cols)} - 1px)`
|
|
2228
|
+
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
2152
2229
|
});
|
|
2153
|
-
link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
|
|
2154
|
-
c.more.append(link);
|
|
2155
2230
|
}
|
|
2156
2231
|
}
|
|
2157
2232
|
meta(s, bg) {
|
|
@@ -2262,6 +2337,26 @@ var TimeGridView = class {
|
|
|
2262
2337
|
}
|
|
2263
2338
|
resize() {
|
|
2264
2339
|
this.fitRows();
|
|
2340
|
+
this.render(null);
|
|
2341
|
+
}
|
|
2342
|
+
revealDay(day) {
|
|
2343
|
+
const column = this.cols.find((candidate) => candidate.day === day);
|
|
2344
|
+
if (!column) return;
|
|
2345
|
+
const bounds = column.head.getBoundingClientRect();
|
|
2346
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2347
|
+
this.scroller.scrollLeft += bounds.left - viewport.left - (viewport.width - bounds.width) / 2;
|
|
2348
|
+
this.updateDayNavigation();
|
|
2349
|
+
}
|
|
2350
|
+
updateDayNavigation() {
|
|
2351
|
+
this.dayNavigation.hidden = this.scroller.scrollWidth <= this.scroller.clientWidth + 1;
|
|
2352
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2353
|
+
this.cols.forEach((column, index) => {
|
|
2354
|
+
const bounds = column.head.getBoundingClientRect();
|
|
2355
|
+
const visible = bounds.left >= viewport.left && bounds.right <= viewport.right;
|
|
2356
|
+
const button = this.dayNavigation.children[index];
|
|
2357
|
+
button?.classList.toggle("is-offscreen", !visible);
|
|
2358
|
+
button?.setAttribute("aria-pressed", String(visible));
|
|
2359
|
+
});
|
|
2265
2360
|
}
|
|
2266
2361
|
scrollToTime(minute) {
|
|
2267
2362
|
if (!this.body) return;
|
|
@@ -2934,6 +3029,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2934
3029
|
|
|
2935
3030
|
// src/dom/calendar.ts
|
|
2936
3031
|
var EXTERNAL_ID = "__tnc_external__";
|
|
3032
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
2937
3033
|
var MAX_AGENDA_DAYS = 366;
|
|
2938
3034
|
function sameOption(a, b, depth = 0) {
|
|
2939
3035
|
if (a === b) return true;
|
|
@@ -3026,7 +3122,11 @@ var Calendar = class {
|
|
|
3026
3122
|
this.resizeObs?.disconnect();
|
|
3027
3123
|
const RO = this.win.ResizeObserver;
|
|
3028
3124
|
if (RO) {
|
|
3029
|
-
this.resizeObs = new RO(() =>
|
|
3125
|
+
this.resizeObs = new RO(() => {
|
|
3126
|
+
this.syncToolbarViewDisplay();
|
|
3127
|
+
this.current?.resize?.();
|
|
3128
|
+
this.positionMore();
|
|
3129
|
+
});
|
|
3030
3130
|
this.resizeObs.observe(this.root);
|
|
3031
3131
|
}
|
|
3032
3132
|
}
|
|
@@ -3046,9 +3146,11 @@ var Calendar = class {
|
|
|
3046
3146
|
if (this.frame !== null) old.cancelAnimationFrame(this.frame);
|
|
3047
3147
|
this.timer = this.frame = null;
|
|
3048
3148
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3149
|
+
this.listenMoreGeometry(false);
|
|
3049
3150
|
this.doc = doc;
|
|
3050
3151
|
this.win = doc.defaultView ?? old;
|
|
3051
3152
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3153
|
+
this.listenMoreGeometry(true);
|
|
3052
3154
|
this.observeResize();
|
|
3053
3155
|
this.rebuild(false);
|
|
3054
3156
|
this.scheduleTick();
|
|
@@ -3166,6 +3268,7 @@ var Calendar = class {
|
|
|
3166
3268
|
goToday() {
|
|
3167
3269
|
this.anchor = this.todayDay();
|
|
3168
3270
|
this.rebuild(true);
|
|
3271
|
+
if (this.current instanceof TimeGridView) this.current.revealDay(this.anchor);
|
|
3169
3272
|
}
|
|
3170
3273
|
/** The anchor date, as local midnight of that civil day in the calendar zone. */
|
|
3171
3274
|
getDate() {
|
|
@@ -3184,6 +3287,7 @@ var Calendar = class {
|
|
|
3184
3287
|
/** Re-measures (e.g. after the host was shown or resized by a parent). */
|
|
3185
3288
|
updateSize() {
|
|
3186
3289
|
this.current?.resize?.();
|
|
3290
|
+
this.positionMore();
|
|
3187
3291
|
}
|
|
3188
3292
|
getScroll() {
|
|
3189
3293
|
return this.current?.getScroll() ?? { top: 0, left: 0 };
|
|
@@ -3215,7 +3319,10 @@ var Calendar = class {
|
|
|
3215
3319
|
}
|
|
3216
3320
|
const view = this.current;
|
|
3217
3321
|
if (view instanceof TimelineView) view.scrollToInstant(ms, opts.align ?? "center", opts.smooth ?? false);
|
|
3218
|
-
else if (view instanceof TimeGridView
|
|
3322
|
+
else if (view instanceof TimeGridView) {
|
|
3323
|
+
view.revealDay(day);
|
|
3324
|
+
if (p.kind !== "day") view.scrollToTime(wallTime(ms, this.zone).minute);
|
|
3325
|
+
}
|
|
3219
3326
|
}
|
|
3220
3327
|
/** What is under a client point: a time slot, a day, or a timeline row and date. Null outside the view. */
|
|
3221
3328
|
hitTest(clientX, clientY) {
|
|
@@ -3390,9 +3497,10 @@ var Calendar = class {
|
|
|
3390
3497
|
if (meta) this.opts.itemWillUnmount?.(this.itemArg(el, meta));
|
|
3391
3498
|
el.remove();
|
|
3392
3499
|
}
|
|
3393
|
-
openMore(day, anchor, jsEvent) {
|
|
3500
|
+
openMore(day, anchor, jsEvent, itemIds) {
|
|
3394
3501
|
jsEvent.stopPropagation();
|
|
3395
|
-
const
|
|
3502
|
+
const group = itemIds ? new Set(itemIds) : null;
|
|
3503
|
+
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));
|
|
3396
3504
|
const shown = new Set(
|
|
3397
3505
|
[...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 ?? "")
|
|
3398
3506
|
);
|
|
@@ -3421,15 +3529,7 @@ var Calendar = class {
|
|
|
3421
3529
|
}
|
|
3422
3530
|
pool.end();
|
|
3423
3531
|
this.popover = { el, pool, anchor, day };
|
|
3424
|
-
|
|
3425
|
-
const ar = anchor.getBoundingClientRect();
|
|
3426
|
-
const w = Math.max(220, ar.width + 24);
|
|
3427
|
-
let left = ar.left - rr.left - 12;
|
|
3428
|
-
left = Math.max(4, Math.min(left, rr.width - w - 4));
|
|
3429
|
-
let top = ar.top - rr.top - 40;
|
|
3430
|
-
const height = el.offsetHeight;
|
|
3431
|
-
top = Math.max(4, Math.min(top, rr.height - height - 4));
|
|
3432
|
-
setStyle(el, { left, top, width: w });
|
|
3532
|
+
this.positionMore();
|
|
3433
3533
|
el.focus();
|
|
3434
3534
|
}
|
|
3435
3535
|
/* =============================================================== render */
|
|
@@ -3553,8 +3653,10 @@ var Calendar = class {
|
|
|
3553
3653
|
this.current.setScroll({ top: keep.top, left: 0 });
|
|
3554
3654
|
this.current.placeInstant(keep.ms, keep.vx);
|
|
3555
3655
|
} else if (scroll) this.current.setScroll(scroll);
|
|
3556
|
-
else if (this.current instanceof TimeGridView)
|
|
3557
|
-
|
|
3656
|
+
else if (this.current instanceof TimeGridView) {
|
|
3657
|
+
this.current.scrollToTime(this.slot.scroll);
|
|
3658
|
+
this.current.revealDay(this.anchor);
|
|
3659
|
+
} else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
3558
3660
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
3559
3661
|
}
|
|
3560
3662
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -3702,28 +3804,65 @@ var Calendar = class {
|
|
|
3702
3804
|
if (e.icon) b.replaceChildren(e.icon());
|
|
3703
3805
|
return b;
|
|
3704
3806
|
});
|
|
3807
|
+
const chevron = (d) => {
|
|
3808
|
+
const svg = this.doc.createElementNS(SVG_NS, "svg");
|
|
3809
|
+
setAttrs(svg, { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false" });
|
|
3810
|
+
const path = this.doc.createElementNS(SVG_NS, "path");
|
|
3811
|
+
path.setAttribute("d", d);
|
|
3812
|
+
svg.append(path);
|
|
3813
|
+
return svg;
|
|
3814
|
+
};
|
|
3815
|
+
const prev = btn("", this.text.prev, "tnc-btn-prev", () => this.prev());
|
|
3816
|
+
prev.append(chevron("m15 18-6-6 6-6"));
|
|
3817
|
+
const next = btn("", this.text.next, "tnc-btn-next", () => this.next());
|
|
3818
|
+
next.append(chevron("m9 18 6-6-6-6"));
|
|
3705
3819
|
const nav = h(
|
|
3706
3820
|
this.doc,
|
|
3707
3821
|
"div",
|
|
3708
3822
|
"tnc-toolbar-group tnc-toolbar-nav",
|
|
3709
3823
|
null,
|
|
3710
|
-
|
|
3824
|
+
prev,
|
|
3711
3825
|
btn(this.text.today, this.text.today, "tnc-btn-today", () => this.goToday()),
|
|
3712
|
-
|
|
3826
|
+
next
|
|
3713
3827
|
);
|
|
3714
3828
|
const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
|
|
3715
3829
|
const viewGroup = h(this.doc, "div", "tnc-toolbar-group tnc-toolbar-views", { role: "group" });
|
|
3830
|
+
const viewSelect = h(this.doc, "select", "tnc-btn tnc-toolbar-view-select", {
|
|
3831
|
+
"aria-label": this.text.viewHints[this.opts.view] ?? this.text.views[this.opts.view] ?? this.opts.view
|
|
3832
|
+
});
|
|
3716
3833
|
for (const v of views) {
|
|
3717
3834
|
const label = v === "days" ? this.text.views.days ?? `${this.opts.dayCount} days` : this.text.views[v] ?? v;
|
|
3718
3835
|
viewGroup.append(btn(label, this.text.viewHints[v] ?? label, `tnc-btn-view tnc-btn-view-${v}`, () => this.changeView(v), v === this.opts.view));
|
|
3836
|
+
viewSelect.append(h(this.doc, "option", "", { value: v }, label));
|
|
3719
3837
|
}
|
|
3838
|
+
viewSelect.value = this.opts.view;
|
|
3839
|
+
viewSelect.addEventListener("change", () => this.changeView(viewSelect.value));
|
|
3720
3840
|
const start = h(this.doc, "div", "tnc-toolbar-start");
|
|
3721
3841
|
const end = h(this.doc, "div", "tnc-toolbar-end");
|
|
3722
3842
|
if (cfg.extraPlacement === "start") start.append(...extras);
|
|
3723
3843
|
start.append(nav, title);
|
|
3724
|
-
end.append(viewGroup);
|
|
3844
|
+
end.append(viewGroup, viewSelect);
|
|
3725
3845
|
if (cfg.extraPlacement !== "start") end.append(...extras);
|
|
3726
3846
|
this.toolbarEl.append(start, end);
|
|
3847
|
+
this.syncToolbarViewDisplay();
|
|
3848
|
+
}
|
|
3849
|
+
syncToolbarViewDisplay() {
|
|
3850
|
+
const buttons = this.toolbarEl.querySelector(".tnc-toolbar-views");
|
|
3851
|
+
const select = this.toolbarEl.querySelector(".tnc-toolbar-view-select");
|
|
3852
|
+
const start = this.toolbarEl.querySelector(".tnc-toolbar-start");
|
|
3853
|
+
if (!buttons || !select || !start) return;
|
|
3854
|
+
const config = this.opts.toolbar;
|
|
3855
|
+
const mode = typeof config === "object" ? config.viewDisplay ?? "auto" : "auto";
|
|
3856
|
+
buttons.hidden = false;
|
|
3857
|
+
select.hidden = true;
|
|
3858
|
+
buttons.style.display = "flex";
|
|
3859
|
+
select.style.display = "none";
|
|
3860
|
+
const startWidth = Array.from(start.children).reduce((width, child) => width + child.scrollWidth, 0);
|
|
3861
|
+
const compact = mode === "select" || mode === "auto" && startWidth + buttons.scrollWidth + 32 > this.root.clientWidth;
|
|
3862
|
+
buttons.hidden = compact;
|
|
3863
|
+
select.hidden = !compact;
|
|
3864
|
+
buttons.style.display = compact ? "none" : "flex";
|
|
3865
|
+
select.style.display = compact ? "" : "none";
|
|
3727
3866
|
}
|
|
3728
3867
|
applyChange(change) {
|
|
3729
3868
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3856,6 +3995,38 @@ var Calendar = class {
|
|
|
3856
3995
|
}
|
|
3857
3996
|
}
|
|
3858
3997
|
/* ============================================================== popover */
|
|
3998
|
+
onMoreGeometryChange = () => this.positionMore();
|
|
3999
|
+
listenMoreGeometry(on) {
|
|
4000
|
+
const method = on ? "addEventListener" : "removeEventListener";
|
|
4001
|
+
this.doc[method]("scroll", this.onMoreGeometryChange, true);
|
|
4002
|
+
this.win[method]("resize", this.onMoreGeometryChange);
|
|
4003
|
+
this.win.visualViewport?.[method]("resize", this.onMoreGeometryChange);
|
|
4004
|
+
this.win.visualViewport?.[method]("scroll", this.onMoreGeometryChange);
|
|
4005
|
+
}
|
|
4006
|
+
positionMore() {
|
|
4007
|
+
const popover = this.popover;
|
|
4008
|
+
if (!popover) return;
|
|
4009
|
+
if (!popover.anchor.isConnected) {
|
|
4010
|
+
this.closeMore();
|
|
4011
|
+
return;
|
|
4012
|
+
}
|
|
4013
|
+
const viewport = this.win.visualViewport;
|
|
4014
|
+
const root = this.root.getBoundingClientRect();
|
|
4015
|
+
const anchor = popover.anchor.getBoundingClientRect();
|
|
4016
|
+
const leftEdge = Math.max(root.left, viewport?.offsetLeft ?? 0) + 4;
|
|
4017
|
+
const topEdge = Math.max(root.top, viewport?.offsetTop ?? 0) + 4;
|
|
4018
|
+
const rightEdge = Math.min(root.right, (viewport?.offsetLeft ?? 0) + (viewport?.width ?? this.win.innerWidth)) - 4;
|
|
4019
|
+
const bottomEdge = Math.min(root.bottom, (viewport?.offsetTop ?? 0) + (viewport?.height ?? this.win.innerHeight)) - 4;
|
|
4020
|
+
if (rightEdge <= leftEdge || bottomEdge <= topEdge) {
|
|
4021
|
+
this.closeMore();
|
|
4022
|
+
return;
|
|
4023
|
+
}
|
|
4024
|
+
const width = Math.min(360, rightEdge - leftEdge);
|
|
4025
|
+
setStyle(popover.el, { width, "max-height": Math.min(420, bottomEdge - topEdge) });
|
|
4026
|
+
const left = Math.max(leftEdge, Math.min(anchor.left - 12, rightEdge - width));
|
|
4027
|
+
const top = Math.max(topEdge, Math.min(anchor.top - 40, bottomEdge - popover.el.offsetHeight));
|
|
4028
|
+
setStyle(popover.el, { left: left - root.left, top: top - root.top });
|
|
4029
|
+
}
|
|
3859
4030
|
refreshMore() {
|
|
3860
4031
|
const p = this.popover;
|
|
3861
4032
|
if (!p) return;
|
|
@@ -3865,6 +4036,7 @@ var Calendar = class {
|
|
|
3865
4036
|
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 });
|
|
3866
4037
|
}
|
|
3867
4038
|
p.pool.end();
|
|
4039
|
+
this.positionMore();
|
|
3868
4040
|
}
|
|
3869
4041
|
closeMore(restoreFocus = false) {
|
|
3870
4042
|
const p = this.popover;
|
|
@@ -3892,6 +4064,7 @@ var Calendar = class {
|
|
|
3892
4064
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
3893
4065
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
3894
4066
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
4067
|
+
this.listenMoreGeometry(true);
|
|
3895
4068
|
}
|
|
3896
4069
|
unbindEvents() {
|
|
3897
4070
|
const r = this.root;
|
|
@@ -3910,6 +4083,7 @@ var Calendar = class {
|
|
|
3910
4083
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
3911
4084
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
3912
4085
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
4086
|
+
this.listenMoreGeometry(false);
|
|
3913
4087
|
this.listenDoc(false);
|
|
3914
4088
|
}
|
|
3915
4089
|
listenDoc(on) {
|
|
@@ -3955,6 +4129,7 @@ var Calendar = class {
|
|
|
3955
4129
|
mode = "select";
|
|
3956
4130
|
}
|
|
3957
4131
|
const touch = ev.pointerType === "touch";
|
|
4132
|
+
const overflowOrigin = itemEl?.closest(".tnc-popover") && origin ? wallTime(origin.startMs, this.zone) : null;
|
|
3958
4133
|
const s = {
|
|
3959
4134
|
mode,
|
|
3960
4135
|
pointerId: ev.pointerId,
|
|
@@ -3963,7 +4138,7 @@ var Calendar = class {
|
|
|
3963
4138
|
y0: ev.clientY,
|
|
3964
4139
|
active: false,
|
|
3965
4140
|
origin,
|
|
3966
|
-
grab: this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
4141
|
+
grab: overflowOrigin && this.current instanceof TimeGridView && !origin?.allDay ? { kind: "time", day: overflowOrigin.day, minute: overflowOrigin.minute } : this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
3967
4142
|
proposal: null,
|
|
3968
4143
|
denied: false,
|
|
3969
4144
|
timer: null,
|
|
@@ -4466,6 +4641,25 @@ var Calendar = class {
|
|
|
4466
4641
|
if (el && meta) this.opts.onItemHover?.({ ...this.itemArg(el, meta), jsEvent: ev, entering: false });
|
|
4467
4642
|
}
|
|
4468
4643
|
onKeyDown = (ev) => {
|
|
4644
|
+
if (this.popover && !ev.altKey && !ev.ctrlKey && !ev.metaKey && ["ArrowDown", "ArrowUp", "Home", "End"].includes(ev.key)) {
|
|
4645
|
+
const rows = [...this.popover.el.querySelectorAll(".tnc-item")];
|
|
4646
|
+
const index = rows.indexOf(this.doc.activeElement);
|
|
4647
|
+
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;
|
|
4648
|
+
ev.preventDefault();
|
|
4649
|
+
ev.stopPropagation();
|
|
4650
|
+
rows[next]?.focus();
|
|
4651
|
+
rows[next]?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
4652
|
+
return;
|
|
4653
|
+
}
|
|
4654
|
+
if (ev.key === "Tab" && this.popover) {
|
|
4655
|
+
const controls = [...this.popover.el.querySelectorAll('button, a[href], [tabindex="0"]')];
|
|
4656
|
+
const index = controls.indexOf(this.doc.activeElement);
|
|
4657
|
+
const next = ev.shiftKey ? index <= 0 ? controls.length - 1 : index - 1 : (index + 1) % controls.length;
|
|
4658
|
+
ev.preventDefault();
|
|
4659
|
+
ev.stopPropagation();
|
|
4660
|
+
controls[next]?.focus();
|
|
4661
|
+
return;
|
|
4662
|
+
}
|
|
4469
4663
|
if (ev.key === "Escape" && this.popover) {
|
|
4470
4664
|
ev.preventDefault();
|
|
4471
4665
|
ev.stopPropagation();
|