@tasknotes/calendar 0.1.6 → 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 +16 -0
- package/README.md +31 -0
- package/dist/calendar.css +53 -0
- package/dist/cjs/index.cjs +190 -30
- package/dist/cjs/react.cjs +190 -30
- package/dist/esm/{chunk-URE7QLU3.js → chunk-EDRCVOY7.js} +190 -30
- package/dist/esm/index.js +1 -1
- package/dist/esm/react.js +1 -1
- 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",
|
|
@@ -1169,19 +1171,27 @@ var DayGridView = class {
|
|
|
1169
1171
|
});
|
|
1170
1172
|
}
|
|
1171
1173
|
w.pool.end();
|
|
1172
|
-
w.more.
|
|
1174
|
+
const existing = new Map(Array.from(w.more.children, (element) => [element.getAttribute("data-day"), element]));
|
|
1173
1175
|
hidden.forEach((items, col) => {
|
|
1174
1176
|
if (!items.length) return;
|
|
1175
1177
|
const day = w.days[col];
|
|
1176
|
-
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);
|
|
1177
1188
|
setStyle(link, {
|
|
1178
1189
|
left: `calc(${pct(col / n)} + 2px)`,
|
|
1179
1190
|
width: `calc(${pct(1 / n)} - 4px)`,
|
|
1180
1191
|
top: `calc(var(--tnc-dg-top-h) + ${visible(col)} * (var(--tnc-bar-h) + ${BAR_GAP}px))`
|
|
1181
1192
|
});
|
|
1182
|
-
link.addEventListener("click", (ev) => ctx.openMore(day, link, ev));
|
|
1183
|
-
w.more.append(link);
|
|
1184
1193
|
});
|
|
1194
|
+
for (const link of existing.values()) link.remove();
|
|
1185
1195
|
const step = this.barPx + BAR_GAP;
|
|
1186
1196
|
let need = maxLevel >= 0 ? (maxLevel + 1) * step - BAR_GAP : 0;
|
|
1187
1197
|
hidden.forEach((items, col) => {
|
|
@@ -1384,6 +1394,8 @@ var ListView = class {
|
|
|
1384
1394
|
if (!keep.size && !leadItems.length) {
|
|
1385
1395
|
if (!empty) {
|
|
1386
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);
|
|
1387
1399
|
this.body.append(empty);
|
|
1388
1400
|
}
|
|
1389
1401
|
} else empty?.remove();
|
|
@@ -1732,6 +1744,8 @@ var TimeGridView = class {
|
|
|
1732
1744
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1733
1745
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1734
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());
|
|
1735
1749
|
this.top = h(doc, "div", "tnc-tg-top");
|
|
1736
1750
|
this.headRow = h(doc, "div", "tnc-tg-head tnc-tg-cols", { role: "row" });
|
|
1737
1751
|
this.top.append(this.headRow);
|
|
@@ -1759,7 +1773,7 @@ var TimeGridView = class {
|
|
|
1759
1773
|
} else {
|
|
1760
1774
|
this.body = this.gutter = null;
|
|
1761
1775
|
}
|
|
1762
|
-
this.el.append(this.scroller);
|
|
1776
|
+
this.el.append(this.dayNavigation, this.scroller);
|
|
1763
1777
|
this.build();
|
|
1764
1778
|
if (showGrid && opts.height === "fill") {
|
|
1765
1779
|
const RO = ctx.win.ResizeObserver;
|
|
@@ -1772,6 +1786,7 @@ var TimeGridView = class {
|
|
|
1772
1786
|
ctx;
|
|
1773
1787
|
el;
|
|
1774
1788
|
scroller;
|
|
1789
|
+
dayNavigation;
|
|
1775
1790
|
top;
|
|
1776
1791
|
headRow;
|
|
1777
1792
|
allDayRow;
|
|
@@ -1827,6 +1842,9 @@ var TimeGridView = class {
|
|
|
1827
1842
|
head.append(cap);
|
|
1828
1843
|
}
|
|
1829
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);
|
|
1830
1848
|
opts.dayHeaderDidMount?.({ ...arg, el: head });
|
|
1831
1849
|
const col = h(doc, "div", ["tnc-col", ...ctx.dayClasses(day)].join(" "), { role: "cell", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr });
|
|
1832
1850
|
const bgLayer = h(doc, "div", "tnc-col-bg");
|
|
@@ -1843,6 +1861,7 @@ var TimeGridView = class {
|
|
|
1843
1861
|
this.body?.append(col);
|
|
1844
1862
|
return {
|
|
1845
1863
|
day,
|
|
1864
|
+
collisionColumns: 1,
|
|
1846
1865
|
head,
|
|
1847
1866
|
col,
|
|
1848
1867
|
plan: new ItemPool(ctx, planLayer),
|
|
@@ -1899,12 +1918,17 @@ var TimeGridView = class {
|
|
|
1899
1918
|
/* --------------------------------------------------------------- render */
|
|
1900
1919
|
render(change) {
|
|
1901
1920
|
this.renderAllDay();
|
|
1902
|
-
if (!this.body)
|
|
1921
|
+
if (!this.body) {
|
|
1922
|
+
this.updateDayNavigation();
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1903
1925
|
const dirty = change ? change.days : null;
|
|
1904
1926
|
for (const c of this.cols) {
|
|
1905
1927
|
if (dirty && !dirty.has(c.day) && !(this.ctx.slot.max > MINUTES_PER_DAY && dirty.has(c.day + 1))) continue;
|
|
1906
1928
|
this.renderColumn(c);
|
|
1907
1929
|
}
|
|
1930
|
+
this.el.style.setProperty("--tnc-collision-columns", String(Math.max(1, ...this.cols.map((column) => column.collisionColumns))));
|
|
1931
|
+
this.updateDayNavigation();
|
|
1908
1932
|
this.renderCapacity();
|
|
1909
1933
|
this.tick();
|
|
1910
1934
|
}
|
|
@@ -2022,6 +2046,11 @@ var TimeGridView = class {
|
|
|
2022
2046
|
}
|
|
2023
2047
|
const minMinutes = opts.eventMinHeight / this.ppm;
|
|
2024
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
|
+
);
|
|
2025
2054
|
const total = this.minutes;
|
|
2026
2055
|
const top = (m) => pct((m - slot.min) / total);
|
|
2027
2056
|
const height = (s) => pct((s.endMin - s.startMin) / total);
|
|
@@ -2033,14 +2062,22 @@ var TimeGridView = class {
|
|
|
2033
2062
|
c.bg.end();
|
|
2034
2063
|
const hasActual = layout.actual.length > 0;
|
|
2035
2064
|
c.col.classList.toggle("has-actual", hasActual);
|
|
2065
|
+
for (const link of c.more.children) link.setAttribute("data-stale", "true");
|
|
2036
2066
|
this.placeLane(c.plan, layout.plan, c, top, height, "plan");
|
|
2037
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();
|
|
2038
2069
|
}
|
|
2039
2070
|
placeLane(pool, placements, c, top, height, lane) {
|
|
2040
2071
|
const { ctx } = this;
|
|
2041
2072
|
const { opts } = ctx;
|
|
2042
|
-
const
|
|
2043
|
-
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;
|
|
2044
2081
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
2045
2082
|
pool.begin();
|
|
2046
2083
|
for (const p of placements) {
|
|
@@ -2066,21 +2103,38 @@ var TimeGridView = class {
|
|
|
2066
2103
|
top: top(p.startMin),
|
|
2067
2104
|
height: `max(${height(p)}, ${opts.eventMinHeight}px)`,
|
|
2068
2105
|
...inLane(lane, p.split, left, width),
|
|
2106
|
+
"--tnc-event-readable-width": pct(span / cols / width),
|
|
2069
2107
|
"z-index": String(p.col + 1)
|
|
2070
2108
|
});
|
|
2071
2109
|
}
|
|
2072
2110
|
pool.end();
|
|
2073
2111
|
if (!withMore) return;
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
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
|
+
}
|
|
2077
2133
|
const cols = maxStack;
|
|
2078
2134
|
setStyle(link, {
|
|
2079
2135
|
top: top(rec.start),
|
|
2080
2136
|
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
2081
2137
|
});
|
|
2082
|
-
link.addEventListener("click", (ev) => ctx.openMore(c.day, link, ev));
|
|
2083
|
-
c.more.append(link);
|
|
2084
2138
|
}
|
|
2085
2139
|
}
|
|
2086
2140
|
meta(s, bg) {
|
|
@@ -2191,6 +2245,26 @@ var TimeGridView = class {
|
|
|
2191
2245
|
}
|
|
2192
2246
|
resize() {
|
|
2193
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
|
+
});
|
|
2194
2268
|
}
|
|
2195
2269
|
scrollToTime(minute) {
|
|
2196
2270
|
if (!this.body) return;
|
|
@@ -2956,7 +3030,11 @@ var Calendar = class {
|
|
|
2956
3030
|
this.resizeObs?.disconnect();
|
|
2957
3031
|
const RO = this.win.ResizeObserver;
|
|
2958
3032
|
if (RO) {
|
|
2959
|
-
this.resizeObs = new RO(() =>
|
|
3033
|
+
this.resizeObs = new RO(() => {
|
|
3034
|
+
this.syncToolbarViewDisplay();
|
|
3035
|
+
this.current?.resize?.();
|
|
3036
|
+
this.positionMore();
|
|
3037
|
+
});
|
|
2960
3038
|
this.resizeObs.observe(this.root);
|
|
2961
3039
|
}
|
|
2962
3040
|
}
|
|
@@ -2976,9 +3054,11 @@ var Calendar = class {
|
|
|
2976
3054
|
if (this.frame !== null) old.cancelAnimationFrame(this.frame);
|
|
2977
3055
|
this.timer = this.frame = null;
|
|
2978
3056
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3057
|
+
this.listenMoreGeometry(false);
|
|
2979
3058
|
this.doc = doc;
|
|
2980
3059
|
this.win = doc.defaultView ?? old;
|
|
2981
3060
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3061
|
+
this.listenMoreGeometry(true);
|
|
2982
3062
|
this.observeResize();
|
|
2983
3063
|
this.rebuild(false);
|
|
2984
3064
|
this.scheduleTick();
|
|
@@ -3096,6 +3176,7 @@ var Calendar = class {
|
|
|
3096
3176
|
goToday() {
|
|
3097
3177
|
this.anchor = this.todayDay();
|
|
3098
3178
|
this.rebuild(true);
|
|
3179
|
+
if (this.current instanceof TimeGridView) this.current.revealDay(this.anchor);
|
|
3099
3180
|
}
|
|
3100
3181
|
/** The anchor date, as local midnight of that civil day in the calendar zone. */
|
|
3101
3182
|
getDate() {
|
|
@@ -3114,6 +3195,7 @@ var Calendar = class {
|
|
|
3114
3195
|
/** Re-measures (e.g. after the host was shown or resized by a parent). */
|
|
3115
3196
|
updateSize() {
|
|
3116
3197
|
this.current?.resize?.();
|
|
3198
|
+
this.positionMore();
|
|
3117
3199
|
}
|
|
3118
3200
|
getScroll() {
|
|
3119
3201
|
return this.current?.getScroll() ?? { top: 0, left: 0 };
|
|
@@ -3145,7 +3227,10 @@ var Calendar = class {
|
|
|
3145
3227
|
}
|
|
3146
3228
|
const view = this.current;
|
|
3147
3229
|
if (view instanceof TimelineView) view.scrollToInstant(ms, opts.align ?? "center", opts.smooth ?? false);
|
|
3148
|
-
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
|
+
}
|
|
3149
3234
|
}
|
|
3150
3235
|
/** What is under a client point: a time slot, a day, or a timeline row and date. Null outside the view. */
|
|
3151
3236
|
hitTest(clientX, clientY) {
|
|
@@ -3320,9 +3405,10 @@ var Calendar = class {
|
|
|
3320
3405
|
if (meta) this.opts.itemWillUnmount?.(this.itemArg(el, meta));
|
|
3321
3406
|
el.remove();
|
|
3322
3407
|
}
|
|
3323
|
-
openMore(day, anchor, jsEvent) {
|
|
3408
|
+
openMore(day, anchor, jsEvent, itemIds) {
|
|
3324
3409
|
jsEvent.stopPropagation();
|
|
3325
|
-
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));
|
|
3326
3412
|
const shown = new Set(
|
|
3327
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 ?? "")
|
|
3328
3414
|
);
|
|
@@ -3351,15 +3437,7 @@ var Calendar = class {
|
|
|
3351
3437
|
}
|
|
3352
3438
|
pool.end();
|
|
3353
3439
|
this.popover = { el, pool, anchor, day };
|
|
3354
|
-
|
|
3355
|
-
const ar = anchor.getBoundingClientRect();
|
|
3356
|
-
const w = Math.max(220, ar.width + 24);
|
|
3357
|
-
let left = ar.left - rr.left - 12;
|
|
3358
|
-
left = Math.max(4, Math.min(left, rr.width - w - 4));
|
|
3359
|
-
let top = ar.top - rr.top - 40;
|
|
3360
|
-
const height = el.offsetHeight;
|
|
3361
|
-
top = Math.max(4, Math.min(top, rr.height - height - 4));
|
|
3362
|
-
setStyle(el, { left, top, width: w });
|
|
3440
|
+
this.positionMore();
|
|
3363
3441
|
el.focus();
|
|
3364
3442
|
}
|
|
3365
3443
|
/* =============================================================== render */
|
|
@@ -3483,8 +3561,10 @@ var Calendar = class {
|
|
|
3483
3561
|
this.current.setScroll({ top: keep.top, left: 0 });
|
|
3484
3562
|
this.current.placeInstant(keep.ms, keep.vx);
|
|
3485
3563
|
} else if (scroll) this.current.setScroll(scroll);
|
|
3486
|
-
else if (this.current instanceof TimeGridView)
|
|
3487
|
-
|
|
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);
|
|
3488
3568
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
3489
3569
|
}
|
|
3490
3570
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -3655,17 +3735,42 @@ var Calendar = class {
|
|
|
3655
3735
|
);
|
|
3656
3736
|
const title = h(this.doc, "h2", "tnc-title", { "aria-live": "polite" }, this.view.title);
|
|
3657
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
|
+
});
|
|
3658
3741
|
for (const v of views) {
|
|
3659
3742
|
const label = v === "days" ? this.text.views.days ?? `${this.opts.dayCount} days` : this.text.views[v] ?? v;
|
|
3660
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));
|
|
3661
3745
|
}
|
|
3746
|
+
viewSelect.value = this.opts.view;
|
|
3747
|
+
viewSelect.addEventListener("change", () => this.changeView(viewSelect.value));
|
|
3662
3748
|
const start = h(this.doc, "div", "tnc-toolbar-start");
|
|
3663
3749
|
const end = h(this.doc, "div", "tnc-toolbar-end");
|
|
3664
3750
|
if (cfg.extraPlacement === "start") start.append(...extras);
|
|
3665
3751
|
start.append(nav, title);
|
|
3666
|
-
end.append(viewGroup);
|
|
3752
|
+
end.append(viewGroup, viewSelect);
|
|
3667
3753
|
if (cfg.extraPlacement !== "start") end.append(...extras);
|
|
3668
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";
|
|
3669
3774
|
}
|
|
3670
3775
|
applyChange(change) {
|
|
3671
3776
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3798,6 +3903,38 @@ var Calendar = class {
|
|
|
3798
3903
|
}
|
|
3799
3904
|
}
|
|
3800
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
|
+
}
|
|
3801
3938
|
refreshMore() {
|
|
3802
3939
|
const p = this.popover;
|
|
3803
3940
|
if (!p) return;
|
|
@@ -3807,6 +3944,7 @@ var Calendar = class {
|
|
|
3807
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 });
|
|
3808
3945
|
}
|
|
3809
3946
|
p.pool.end();
|
|
3947
|
+
this.positionMore();
|
|
3810
3948
|
}
|
|
3811
3949
|
closeMore(restoreFocus = false) {
|
|
3812
3950
|
const p = this.popover;
|
|
@@ -3834,6 +3972,7 @@ var Calendar = class {
|
|
|
3834
3972
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
3835
3973
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
3836
3974
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3975
|
+
this.listenMoreGeometry(true);
|
|
3837
3976
|
}
|
|
3838
3977
|
unbindEvents() {
|
|
3839
3978
|
const r = this.root;
|
|
@@ -3852,6 +3991,7 @@ var Calendar = class {
|
|
|
3852
3991
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
3853
3992
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
3854
3993
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
3994
|
+
this.listenMoreGeometry(false);
|
|
3855
3995
|
this.listenDoc(false);
|
|
3856
3996
|
}
|
|
3857
3997
|
listenDoc(on) {
|
|
@@ -3897,6 +4037,7 @@ var Calendar = class {
|
|
|
3897
4037
|
mode = "select";
|
|
3898
4038
|
}
|
|
3899
4039
|
const touch = ev.pointerType === "touch";
|
|
4040
|
+
const overflowOrigin = itemEl?.closest(".tnc-popover") && origin ? wallTime(origin.startMs, this.zone) : null;
|
|
3900
4041
|
const s = {
|
|
3901
4042
|
mode,
|
|
3902
4043
|
pointerId: ev.pointerId,
|
|
@@ -3905,7 +4046,7 @@ var Calendar = class {
|
|
|
3905
4046
|
y0: ev.clientY,
|
|
3906
4047
|
active: false,
|
|
3907
4048
|
origin,
|
|
3908
|
-
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,
|
|
3909
4050
|
proposal: null,
|
|
3910
4051
|
denied: false,
|
|
3911
4052
|
timer: null,
|
|
@@ -4408,6 +4549,25 @@ var Calendar = class {
|
|
|
4408
4549
|
if (el && meta) this.opts.onItemHover?.({ ...this.itemArg(el, meta), jsEvent: ev, entering: false });
|
|
4409
4550
|
}
|
|
4410
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
|
+
}
|
|
4411
4571
|
if (ev.key === "Escape" && this.popover) {
|
|
4412
4572
|
ev.preventDefault();
|
|
4413
4573
|
ev.stopPropagation();
|