@tasknotes/calendar 0.1.8 → 0.1.10
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 +18 -1
- package/README.md +1 -0
- package/dist/calendar.css +26 -0
- package/dist/cjs/index.cjs +204 -26
- package/dist/cjs/react.cjs +200 -26
- package/dist/esm/{chunk-EXKFHPW2.js → chunk-5PHSJK36.js} +200 -26
- package/dist/esm/index.js +5 -1
- package/dist/esm/react.js +1 -1
- 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/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.1.10
|
|
4
|
+
|
|
5
|
+
- Freeze Month source layout during a move and draw an inert target placeholder; preserve timed drag width across collision layouts.
|
|
6
|
+
- Contain date-strip touch gestures, reveal the complete target month in Year view, and allow dense collision stacks independently of the overlap preference.
|
|
7
|
+
- Require a short pause at a drag edge before scrolling, so passing through the edge does not shift the grid. Cancellation still restores the original scroll position.
|
|
8
|
+
- Keep the whole requested hour label visible below the sticky day and all-day headers when scrolling to a time.
|
|
9
|
+
- Agenda strip paging moves the list to the focused day; scrollbar and keyboard scrolling update the date highlight after a day is picked. Activity dots show up to three appointments in their configured colours.
|
|
10
|
+
- Hosts can supply one wall-clock `formatTime` callback for event, stack, selection, accessibility and slot labels.
|
|
11
|
+
- Hosts can set `overlapStackThreshold` to group dense overlapping appointments into the existing collision stack even in wide columns. Explicit side-by-side layouts remain unchanged.
|
|
12
|
+
|
|
13
|
+
## 0.1.9
|
|
14
|
+
|
|
15
|
+
Not published to npm (the release check failed); these changes ship in 0.1.10.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Pinching a time grid on touch rescales its slots around the time under the fingers. `onSlotHeightChange` reports the settled height for hosts to store, and `pinchZoom: false` turns the gesture off.
|
|
20
|
+
- `basesLayoutOptions` reads `slotHeight` and `pinchZoom` from Bases calendar settings.
|
|
4
21
|
|
|
5
22
|
## 0.1.8
|
|
6
23
|
|
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ The host persists changes; the calendar supplies optimistic updates and a
|
|
|
46
46
|
- **Changes** (`onItemDrop`, `onItemResize`, `onSelect`, `onExternalDrop`) report TaskNotes storage strings (`startStr`/`endStr`), `revert()`, and `source: "pointer" | "keyboard"`. Returning a rejected promise reverts.
|
|
47
47
|
- **Clicks**: `onDateClick` reports `minute` (the slot's wall-clock minute, snapped to `slotDuration`; null for day cells and the all-day row), `allDay` and `startStr` (`"YYYY-MM-DD"` or `"YYYY-MM-DDTHH:mm"`), so a tap on a slot can prefill a time.
|
|
48
48
|
- **Input**: mouse drag after 4px, touch long press (`longPressDelay`), keyboard (Enter/Space, Shift+F10, Alt+Arrow to move, Alt+Shift+Arrow to resize), external drags via `beginExternalDrag` or native HTML5 drops.
|
|
49
|
+
- **Pinch**: two fingers on a time grid rescale `slotHeight` between 16 and 96px, keeping the time under the fingers in place. `onSlotHeightChange` reports the settled height for the host to store; `pinchZoom: false` leaves the gesture to the browser.
|
|
49
50
|
- **Popouts**: all listeners, timers and observers use the host element's `ownerDocument`; bare `window`/`document` are lint errors in `src`.
|
|
50
51
|
|
|
51
52
|
### Views and options
|
package/dist/calendar.css
CHANGED
|
@@ -707,6 +707,10 @@
|
|
|
707
707
|
.tnc-tg-body {
|
|
708
708
|
position: relative;
|
|
709
709
|
}
|
|
710
|
+
.tnc-tg--pinch .tnc-tg-body {
|
|
711
|
+
/* Pinches rescale the grid rather than zoom the page. */
|
|
712
|
+
touch-action: pan-x pan-y;
|
|
713
|
+
}
|
|
710
714
|
.tnc-gutter {
|
|
711
715
|
border-right: 1px solid var(--tnc-line);
|
|
712
716
|
}
|
|
@@ -1196,6 +1200,12 @@
|
|
|
1196
1200
|
font-weight: 650;
|
|
1197
1201
|
line-height: 1;
|
|
1198
1202
|
}
|
|
1203
|
+
.tnc-strip-dots {
|
|
1204
|
+
display: flex;
|
|
1205
|
+
justify-content: center;
|
|
1206
|
+
gap: 2px;
|
|
1207
|
+
min-height: 5px;
|
|
1208
|
+
}
|
|
1199
1209
|
.tnc-strip-dot {
|
|
1200
1210
|
width: 5px;
|
|
1201
1211
|
height: 5px;
|
|
@@ -1906,6 +1916,22 @@
|
|
|
1906
1916
|
outline: 1px solid var(--tnc-focus);
|
|
1907
1917
|
outline-offset: -1px;
|
|
1908
1918
|
}
|
|
1919
|
+
.tnc .tnc-item--timed.is-dragging {
|
|
1920
|
+
min-width: var(--tnc-drag-width);
|
|
1921
|
+
max-width: var(--tnc-drag-width);
|
|
1922
|
+
}
|
|
1923
|
+
.tnc .tnc-dg .tnc-item.is-dragging:not(.is-mirror) {
|
|
1924
|
+
opacity: var(--tnc-drag-opacity);
|
|
1925
|
+
box-shadow: none;
|
|
1926
|
+
outline: none;
|
|
1927
|
+
}
|
|
1928
|
+
.tnc .tnc-month-preview {
|
|
1929
|
+
position: absolute;
|
|
1930
|
+
pointer-events: none;
|
|
1931
|
+
outline: 2px dashed var(--tnc-accent);
|
|
1932
|
+
outline-offset: -2px;
|
|
1933
|
+
z-index: 10;
|
|
1934
|
+
}
|
|
1909
1935
|
.tnc .tnc-item--list.is-dragging:not(.tnc-drag-preview) {
|
|
1910
1936
|
opacity: var(--tnc-drag-opacity);
|
|
1911
1937
|
box-shadow: none;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -832,14 +832,16 @@ function pct(n) {
|
|
|
832
832
|
|
|
833
833
|
// src/dom/format.ts
|
|
834
834
|
var Formatter = class {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
hour12;
|
|
838
|
-
constructor(locale, hour12) {
|
|
835
|
+
constructor(locale, hour12, formatTime) {
|
|
836
|
+
this.formatTime = formatTime;
|
|
839
837
|
this.locale = locale;
|
|
840
838
|
const probe = new Intl.DateTimeFormat(locale, { hour: "numeric" }).resolvedOptions();
|
|
841
839
|
this.hour12 = hour12 ?? (probe.hour12 === true || probe.hourCycle === "h12" || probe.hourCycle === "h11");
|
|
842
840
|
}
|
|
841
|
+
formatTime;
|
|
842
|
+
cache = /* @__PURE__ */ new Map();
|
|
843
|
+
locale;
|
|
844
|
+
hour12;
|
|
843
845
|
fmt(key, opts) {
|
|
844
846
|
let f = this.cache.get(key);
|
|
845
847
|
if (!f) {
|
|
@@ -897,6 +899,7 @@ var Formatter = class {
|
|
|
897
899
|
/** Wall-clock minutes to a label. `compact` drops ":00" in 12-hour formats ("9am"). */
|
|
898
900
|
time(minute, compact = false) {
|
|
899
901
|
const m = (Math.round(minute) % 1440 + 1440) % 1440;
|
|
902
|
+
if (this.formatTime) return this.formatTime(m);
|
|
900
903
|
const date = new Date(Date.UTC(2e3, 0, 1, Math.floor(m / 60), m % 60));
|
|
901
904
|
if (this.hour12) {
|
|
902
905
|
const parts = this.fmt("t12", { hour: "numeric", minute: "2-digit", hour12: true }).formatToParts(date);
|
|
@@ -988,6 +991,7 @@ function defaultOptions() {
|
|
|
988
991
|
snapDuration: void 0,
|
|
989
992
|
scrollTime: "08:00",
|
|
990
993
|
slotHeight: 24,
|
|
994
|
+
pinchZoom: true,
|
|
991
995
|
expandRows: false,
|
|
992
996
|
nowIndicator: true,
|
|
993
997
|
selectMirror: true,
|
|
@@ -1029,6 +1033,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
1029
1033
|
"locale",
|
|
1030
1034
|
"firstDay",
|
|
1031
1035
|
"hour12",
|
|
1036
|
+
"formatTime",
|
|
1032
1037
|
"weekNumbers",
|
|
1033
1038
|
"weekends",
|
|
1034
1039
|
"hiddenDays",
|
|
@@ -1041,9 +1046,11 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
1041
1046
|
"slotDuration",
|
|
1042
1047
|
"snapDuration",
|
|
1043
1048
|
"slotHeight",
|
|
1049
|
+
"pinchZoom",
|
|
1044
1050
|
"expandRows",
|
|
1045
1051
|
"nowIndicator",
|
|
1046
1052
|
"slotEventOverlap",
|
|
1053
|
+
"overlapStackThreshold",
|
|
1047
1054
|
"eventMinHeight",
|
|
1048
1055
|
"eventMaxStack",
|
|
1049
1056
|
"dayMaxEvents",
|
|
@@ -1158,6 +1165,7 @@ var DayGridView = class {
|
|
|
1158
1165
|
topPx = 24;
|
|
1159
1166
|
lastHeights = "";
|
|
1160
1167
|
dotsMode;
|
|
1168
|
+
dragPreviews = [];
|
|
1161
1169
|
daysPerWeek() {
|
|
1162
1170
|
const { days } = this.ctx;
|
|
1163
1171
|
const first = days[0];
|
|
@@ -1171,6 +1179,36 @@ var DayGridView = class {
|
|
|
1171
1179
|
}
|
|
1172
1180
|
render(_change) {
|
|
1173
1181
|
const { ctx } = this;
|
|
1182
|
+
this.clearDragPreviews();
|
|
1183
|
+
const dragged = ctx.draggingId ? ctx.store.get(ctx.draggingId) : void 0;
|
|
1184
|
+
const source = dragged ? this.elementsFor(dragged.id)[0] : void 0;
|
|
1185
|
+
if (dragged && source && this.el.closest(".tnc-dragging-move")) {
|
|
1186
|
+
const proposals = [dragged, ...[...ctx.store.all()].filter((item) => item.id !== dragged.id && item.item.preview)];
|
|
1187
|
+
for (const proposal of proposals) {
|
|
1188
|
+
const original = this.elementsFor(proposal.id)[0] ?? source;
|
|
1189
|
+
const bounds = original.getBoundingClientRect();
|
|
1190
|
+
const sourceWeek = original.closest(".tnc-dg-week");
|
|
1191
|
+
const offset = bounds.top - sourceWeek.getBoundingClientRect().top;
|
|
1192
|
+
for (const week of this.weeks) {
|
|
1193
|
+
const start = week.days.findIndex((day) => day >= proposal.startDay && day < proposal.endDay);
|
|
1194
|
+
if (start < 0) continue;
|
|
1195
|
+
let end = start + 1;
|
|
1196
|
+
while (!this.dotsMode && end < week.days.length && week.days[end] < proposal.endDay) end++;
|
|
1197
|
+
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 });
|
|
1198
|
+
preview.classList.add("tnc-month-preview");
|
|
1199
|
+
preview.inert = true;
|
|
1200
|
+
setStyle(preview, {
|
|
1201
|
+
left: `calc(${pct(start / week.days.length)} + 2px)`,
|
|
1202
|
+
width: `calc(${pct((end - start) / week.days.length)} - 4px)`,
|
|
1203
|
+
top: `${Math.max(this.topPx, Math.min(offset, week.row.clientHeight - bounds.height - 4))}px`,
|
|
1204
|
+
height: `${bounds.height}px`
|
|
1205
|
+
});
|
|
1206
|
+
week.layer.append(preview);
|
|
1207
|
+
this.dragPreviews.push(preview);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1174
1212
|
this.dotsMode = ctx.opts.monthDisplay !== "full" || this.grid.clientWidth / this.daysPerWeek() < 72;
|
|
1175
1213
|
this.el.classList.toggle("tnc-dg--dots", this.dotsMode);
|
|
1176
1214
|
this.el.classList.toggle("tnc-dg--compact", this.dotsMode && ctx.opts.monthDisplay === "full");
|
|
@@ -1202,6 +1240,10 @@ var DayGridView = class {
|
|
|
1202
1240
|
}
|
|
1203
1241
|
}
|
|
1204
1242
|
}
|
|
1243
|
+
clearDragPreviews() {
|
|
1244
|
+
for (const preview of this.dragPreviews) this.ctx.destroyItemEl(preview);
|
|
1245
|
+
this.dragPreviews = [];
|
|
1246
|
+
}
|
|
1205
1247
|
/** Height of a "+N more" link as the host styles it (`--tnc-more-h`, or a taller min-height). */
|
|
1206
1248
|
measureMore(cs) {
|
|
1207
1249
|
const fallback = px(cs, "--tnc-more-h", this.barPx);
|
|
@@ -1393,6 +1435,7 @@ var DayGridView = class {
|
|
|
1393
1435
|
return this.weeks.flatMap((w) => [...w.pool.elementsFor(id), ...(w.dots ?? []).flatMap((c) => c.pool.elementsFor(id))]);
|
|
1394
1436
|
}
|
|
1395
1437
|
destroy() {
|
|
1438
|
+
this.clearDragPreviews();
|
|
1396
1439
|
this.resizeObs?.disconnect();
|
|
1397
1440
|
for (const w of this.weeks) {
|
|
1398
1441
|
w.pool.clear();
|
|
@@ -1445,6 +1488,7 @@ var ListView = class {
|
|
|
1445
1488
|
frame = null;
|
|
1446
1489
|
/** A picked day stays marked until the list is scrolled by hand. */
|
|
1447
1490
|
pinned = false;
|
|
1491
|
+
pinnedTop = 0;
|
|
1448
1492
|
itemsOn(day) {
|
|
1449
1493
|
const { store, opts } = this.ctx;
|
|
1450
1494
|
const out = store.spanningIn(day, day + 1, true);
|
|
@@ -1576,6 +1620,7 @@ var ListView = class {
|
|
|
1576
1620
|
this.scroller.scrollTop += section.getBoundingClientRect().top - this.scroller.getBoundingClientRect().top;
|
|
1577
1621
|
this.ctx.strip?.setFocus(day);
|
|
1578
1622
|
this.pinned = true;
|
|
1623
|
+
this.pinnedTop = this.scroller.scrollTop;
|
|
1579
1624
|
}
|
|
1580
1625
|
fold = () => {
|
|
1581
1626
|
this.pinned = false;
|
|
@@ -1583,6 +1628,7 @@ var ListView = class {
|
|
|
1583
1628
|
if (strip?.expanded) strip.setExpanded(false);
|
|
1584
1629
|
};
|
|
1585
1630
|
onScroll = () => {
|
|
1631
|
+
if (this.pinned && Math.abs(this.scroller.scrollTop - this.pinnedTop) > 1) this.pinned = false;
|
|
1586
1632
|
if (this.frame !== null) return;
|
|
1587
1633
|
this.frame = this.ctx.win.requestAnimationFrame(() => {
|
|
1588
1634
|
this.frame = null;
|
|
@@ -1593,7 +1639,7 @@ var ListView = class {
|
|
|
1593
1639
|
followTop() {
|
|
1594
1640
|
const strip = this.ctx.strip;
|
|
1595
1641
|
if (!strip || this.pinned) return;
|
|
1596
|
-
const edge = strip.el.getBoundingClientRect().bottom + 1;
|
|
1642
|
+
const edge = Math.max(strip.el.getBoundingClientRect().bottom, this.scroller.getBoundingClientRect().top) + 1;
|
|
1597
1643
|
for (const day of this.ctx.days) {
|
|
1598
1644
|
const section = this.sections.get(day);
|
|
1599
1645
|
if (section && section.getBoundingClientRect().bottom > edge) {
|
|
@@ -1641,10 +1687,11 @@ var ListView = class {
|
|
|
1641
1687
|
// src/dom/views/strip.ts
|
|
1642
1688
|
var SWIPE_PX = 40;
|
|
1643
1689
|
var DateStrip = class {
|
|
1644
|
-
constructor(ctx, pick, focus, onShown) {
|
|
1690
|
+
constructor(ctx, pick, focus, onShown, onPage) {
|
|
1645
1691
|
this.ctx = ctx;
|
|
1646
1692
|
this.pick = pick;
|
|
1647
1693
|
this.onShown = onShown;
|
|
1694
|
+
this.onPage = onPage;
|
|
1648
1695
|
const { doc, text } = ctx;
|
|
1649
1696
|
this.focus = focus;
|
|
1650
1697
|
this.shown = startOfWeek(focus, ctx.firstDay);
|
|
@@ -1668,6 +1715,10 @@ var DateStrip = class {
|
|
|
1668
1715
|
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
1669
1716
|
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
1670
1717
|
this.el.addEventListener("pointerup", this.onPointerUp);
|
|
1718
|
+
this.el.addEventListener("touchstart", this.ownTouch, { passive: true });
|
|
1719
|
+
this.el.addEventListener("touchmove", this.ownTouch, { passive: true });
|
|
1720
|
+
this.el.addEventListener("touchend", this.ownTouch, { passive: true });
|
|
1721
|
+
this.el.addEventListener("touchcancel", this.ownTouch, { passive: true });
|
|
1671
1722
|
this.el.addEventListener("pointercancel", this.onPointerCancel);
|
|
1672
1723
|
this.el.addEventListener("click", this.onClickCapture, true);
|
|
1673
1724
|
this.draw();
|
|
@@ -1676,6 +1727,7 @@ var DateStrip = class {
|
|
|
1676
1727
|
ctx;
|
|
1677
1728
|
pick;
|
|
1678
1729
|
onShown;
|
|
1730
|
+
onPage;
|
|
1679
1731
|
el;
|
|
1680
1732
|
title;
|
|
1681
1733
|
dows;
|
|
@@ -1734,6 +1786,7 @@ var DateStrip = class {
|
|
|
1734
1786
|
step(dir) {
|
|
1735
1787
|
this.shown = this.expanded ? addMonths(this.shown, dir) : this.shown + 7 * dir;
|
|
1736
1788
|
this.draw();
|
|
1789
|
+
this.onPage?.(this.expanded ? this.shown : this.focus + 7 * dir);
|
|
1737
1790
|
}
|
|
1738
1791
|
days() {
|
|
1739
1792
|
if (!this.expanded) return Array.from({ length: 7 }, (_, i) => this.shown + i);
|
|
@@ -1758,7 +1811,8 @@ var DateStrip = class {
|
|
|
1758
1811
|
const tabDay = days.includes(this.focus) ? this.focus : days[0];
|
|
1759
1812
|
const cells = days.map((d) => {
|
|
1760
1813
|
const arg = ctx.dayArg(d);
|
|
1761
|
-
const
|
|
1814
|
+
const items = store.inRange(d, d + 1).filter((n) => n.item.display !== "background" && n.item.display !== "none");
|
|
1815
|
+
const count = items.length;
|
|
1762
1816
|
const first = ymdOfDay(d).day === 1 && d !== days[0] && !this.expanded;
|
|
1763
1817
|
const label = fmt.longDate(d);
|
|
1764
1818
|
const cell = h(
|
|
@@ -1782,7 +1836,15 @@ var DateStrip = class {
|
|
|
1782
1836
|
}
|
|
1783
1837
|
);
|
|
1784
1838
|
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))
|
|
1839
|
+
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d)));
|
|
1840
|
+
const dots = h(doc, "span", "tnc-strip-dots", { "aria-hidden": "true" });
|
|
1841
|
+
for (const item of items.slice(0, 3)) {
|
|
1842
|
+
const dot = h(doc, "span", "tnc-strip-dot");
|
|
1843
|
+
const color = item.item.color || item.item.borderColor || item.item.backgroundColor;
|
|
1844
|
+
if (color) dot.style.backgroundColor = color;
|
|
1845
|
+
dots.append(dot);
|
|
1846
|
+
}
|
|
1847
|
+
cell.append(dots);
|
|
1786
1848
|
return cell;
|
|
1787
1849
|
});
|
|
1788
1850
|
this.grid.replaceChildren(...cells);
|
|
@@ -1822,6 +1884,9 @@ var DateStrip = class {
|
|
|
1822
1884
|
if (ev.button !== 0 || this.ctx.draggingId) return;
|
|
1823
1885
|
this.swipe = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
|
|
1824
1886
|
};
|
|
1887
|
+
ownTouch = (event) => {
|
|
1888
|
+
event.stopPropagation();
|
|
1889
|
+
};
|
|
1825
1890
|
onPointerUp = (ev) => {
|
|
1826
1891
|
const s = this.swipe;
|
|
1827
1892
|
this.swipe = null;
|
|
@@ -1861,6 +1926,7 @@ var TimeGridView = class {
|
|
|
1861
1926
|
const showGrid = opts.timeGrid;
|
|
1862
1927
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1863
1928
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1929
|
+
this.el.classList.toggle("tnc-tg--pinch", showGrid && opts.pinchZoom);
|
|
1864
1930
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1865
1931
|
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1866
1932
|
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
@@ -2010,6 +2076,11 @@ var TimeGridView = class {
|
|
|
2010
2076
|
}
|
|
2011
2077
|
if (this.nowGutter) gutter.append(this.nowGutter);
|
|
2012
2078
|
}
|
|
2079
|
+
/** Slot height as drawn, which `expandRows` can stretch past the option. */
|
|
2080
|
+
get drawnSlotHeight() {
|
|
2081
|
+
return this.ctx.slot.duration * this.ppm;
|
|
2082
|
+
}
|
|
2083
|
+
/** Applies `slotHeight`. Items are placed in percentages, so they follow without a render. */
|
|
2013
2084
|
applyScale() {
|
|
2014
2085
|
const { opts, slot } = this.ctx;
|
|
2015
2086
|
this.ppm = opts.slotHeight / slot.duration;
|
|
@@ -2190,15 +2261,17 @@ var TimeGridView = class {
|
|
|
2190
2261
|
const { opts } = ctx;
|
|
2191
2262
|
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
2192
2263
|
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
2193
|
-
const
|
|
2264
|
+
const overlapThreshold = opts.overlapStackThreshold ?? 0;
|
|
2265
|
+
const collapsedClusters = new Set(placements.filter((placement) => {
|
|
2194
2266
|
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2267
|
+
const denseOverlap = overlapThreshold > 1 && placement.cols >= overlapThreshold;
|
|
2268
|
+
return columns > 1 && (columns * readableWidth > laneWidth || denseOverlap);
|
|
2269
|
+
}).map((placement) => placement.cluster));
|
|
2199
2270
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
2200
2271
|
pool.begin();
|
|
2201
2272
|
for (const p of placements) {
|
|
2273
|
+
const withMore = lane === "plan" || collapsedClusters.has(p.cluster);
|
|
2274
|
+
const maxStack = collapsedClusters.has(p.cluster) ? 1 : opts.eventMaxStack;
|
|
2202
2275
|
const preview = p.item.item.preview || p.item.id === ctx.draggingId;
|
|
2203
2276
|
if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
|
|
2204
2277
|
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
|
|
@@ -2211,7 +2284,7 @@ var TimeGridView = class {
|
|
|
2211
2284
|
}
|
|
2212
2285
|
const cols = !preview && withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
|
|
2213
2286
|
const span = Math.min(p.span, cols - p.col);
|
|
2214
|
-
const left = p.col / cols;
|
|
2287
|
+
const left = p.item.id === ctx.draggingId ? 0 : p.col / cols;
|
|
2215
2288
|
let width = span / cols;
|
|
2216
2289
|
if (opts.slotEventOverlap && cols > 1 && p.col + span < cols) width = Math.min(1 - left, width * 1.7);
|
|
2217
2290
|
const el = pool.place(p.item.id, this.meta(p, false));
|
|
@@ -2226,8 +2299,9 @@ var TimeGridView = class {
|
|
|
2226
2299
|
});
|
|
2227
2300
|
}
|
|
2228
2301
|
pool.end();
|
|
2229
|
-
if (!withMore) return;
|
|
2230
2302
|
for (const [cluster, rec] of hiddenByCluster) {
|
|
2303
|
+
const collapse = collapsedClusters.has(cluster);
|
|
2304
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
2231
2305
|
const key = `${lane}-${cluster}`;
|
|
2232
2306
|
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
2233
2307
|
if (!link) {
|
|
@@ -2393,7 +2467,9 @@ var TimeGridView = class {
|
|
|
2393
2467
|
scrollToTime(minute) {
|
|
2394
2468
|
if (!this.body) return;
|
|
2395
2469
|
const y = (minute - this.ctx.slot.min) * this.ppm;
|
|
2396
|
-
|
|
2470
|
+
const label = this.gutter?.querySelector(".tnc-slot-label");
|
|
2471
|
+
const inset = label ? Math.ceil(label.getBoundingClientRect().height / 2) + 2 : 0;
|
|
2472
|
+
this.scroller.scrollTop = Math.max(0, y - inset);
|
|
2397
2473
|
}
|
|
2398
2474
|
getScroll() {
|
|
2399
2475
|
return { top: this.scroller.scrollTop, left: this.scroller.scrollLeft };
|
|
@@ -3057,7 +3133,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
3057
3133
|
const bounds = month.getBoundingClientRect();
|
|
3058
3134
|
const viewport = this.scroller.getBoundingClientRect();
|
|
3059
3135
|
if (bounds.top < viewport.top) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
3060
|
-
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.
|
|
3136
|
+
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
3061
3137
|
}
|
|
3062
3138
|
elementsFor(_id) {
|
|
3063
3139
|
return [];
|
|
@@ -3071,6 +3147,8 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
3071
3147
|
var EXTERNAL_ID = "__tnc_external__";
|
|
3072
3148
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
3073
3149
|
var MAX_AGENDA_DAYS = 366;
|
|
3150
|
+
var PINCH_MIN_SLOT = 16;
|
|
3151
|
+
var PINCH_MAX_SLOT = 96;
|
|
3074
3152
|
function sameOption(a, b, depth = 0) {
|
|
3075
3153
|
if (a === b) return true;
|
|
3076
3154
|
if (typeof a === "function" && typeof b === "function") return true;
|
|
@@ -3098,7 +3176,7 @@ var Calendar = class {
|
|
|
3098
3176
|
this.win = this.doc.defaultView ?? globalThis;
|
|
3099
3177
|
this.opts = { ...defaultOptions(), ...options };
|
|
3100
3178
|
this.zone = getTimeZone(this.opts.timeZone);
|
|
3101
|
-
this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
3179
|
+
this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
3102
3180
|
this.text = this.resolveText();
|
|
3103
3181
|
this.order = parseOrder(this.opts.eventOrder);
|
|
3104
3182
|
this.store = new ItemStore(this.zone, {
|
|
@@ -3140,13 +3218,16 @@ var Calendar = class {
|
|
|
3140
3218
|
draggingId = null;
|
|
3141
3219
|
listDragPreview = null;
|
|
3142
3220
|
dragSources = [];
|
|
3221
|
+
dragWidth = null;
|
|
3143
3222
|
dragScrollFrame = null;
|
|
3144
3223
|
dragScrollOrigins = /* @__PURE__ */ new Map();
|
|
3224
|
+
dragScrollEdges = /* @__PURE__ */ new Map();
|
|
3145
3225
|
itemWriteStates = /* @__PURE__ */ new Map();
|
|
3146
3226
|
dragRow = null;
|
|
3147
3227
|
strip = null;
|
|
3148
3228
|
stripSize = null;
|
|
3149
3229
|
rebuilding = false;
|
|
3230
|
+
pinch = null;
|
|
3150
3231
|
toolbarEl;
|
|
3151
3232
|
viewHost;
|
|
3152
3233
|
current = null;
|
|
@@ -3255,7 +3336,7 @@ var Calendar = class {
|
|
|
3255
3336
|
defaultAllDayDays: this.opts.defaultAllDayDays
|
|
3256
3337
|
});
|
|
3257
3338
|
}
|
|
3258
|
-
if (has("locale") || has("hour12")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
3339
|
+
if (has("locale") || has("hour12") || has("formatTime")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
3259
3340
|
if (has("text") || has("locale")) this.text = this.resolveText();
|
|
3260
3341
|
if (has("eventOrder")) this.order = parseOrder(this.opts.eventOrder);
|
|
3261
3342
|
if (has("date")) this.anchor = this.toDay(this.opts.date) ?? this.anchor;
|
|
@@ -3494,7 +3575,10 @@ var Calendar = class {
|
|
|
3494
3575
|
if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
|
|
3495
3576
|
const indicator = variant === "indicator";
|
|
3496
3577
|
if (!preview && !indicator && (this.isEditable(n, "start", true) || this.isEditable(n, "duration", true))) el.classList.add("is-draggable");
|
|
3497
|
-
if (this.draggingId === n.id)
|
|
3578
|
+
if (this.draggingId === n.id) {
|
|
3579
|
+
el.classList.add("is-dragging");
|
|
3580
|
+
if (variant === "timed" && this.dragWidth !== null) el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
3581
|
+
}
|
|
3498
3582
|
if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
|
|
3499
3583
|
el.classList.add(...classList(it.className));
|
|
3500
3584
|
const arg = this.itemArg(el, meta);
|
|
@@ -3762,7 +3846,9 @@ var Calendar = class {
|
|
|
3762
3846
|
this.root.classList.add("tnc--has-strip");
|
|
3763
3847
|
const { startDay, endDay } = this.view.range;
|
|
3764
3848
|
if (!this.strip) {
|
|
3765
|
-
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown())
|
|
3849
|
+
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown(), (day) => {
|
|
3850
|
+
if (this.agendaList()) this.showListDay(day);
|
|
3851
|
+
});
|
|
3766
3852
|
this.strip = strip;
|
|
3767
3853
|
this.root.insertBefore(strip.el, this.viewHost);
|
|
3768
3854
|
const RO = this.win.ResizeObserver;
|
|
@@ -4126,7 +4212,10 @@ var Calendar = class {
|
|
|
4126
4212
|
r.addEventListener("dragover", this.onNativeDragOver);
|
|
4127
4213
|
r.addEventListener("dragleave", this.onNativeDragLeave);
|
|
4128
4214
|
r.addEventListener("drop", this.onNativeDrop);
|
|
4215
|
+
r.addEventListener("touchstart", this.onTouchStart, { passive: true });
|
|
4129
4216
|
r.addEventListener("touchmove", this.onTouchMove, { passive: false });
|
|
4217
|
+
r.addEventListener("touchend", this.onTouchEnd);
|
|
4218
|
+
r.addEventListener("touchcancel", this.onTouchEnd);
|
|
4130
4219
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
4131
4220
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
4132
4221
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -4145,7 +4234,10 @@ var Calendar = class {
|
|
|
4145
4234
|
r.removeEventListener("dragover", this.onNativeDragOver);
|
|
4146
4235
|
r.removeEventListener("dragleave", this.onNativeDragLeave);
|
|
4147
4236
|
r.removeEventListener("drop", this.onNativeDrop);
|
|
4237
|
+
r.removeEventListener("touchstart", this.onTouchStart);
|
|
4148
4238
|
r.removeEventListener("touchmove", this.onTouchMove);
|
|
4239
|
+
r.removeEventListener("touchend", this.onTouchEnd);
|
|
4240
|
+
r.removeEventListener("touchcancel", this.onTouchEnd);
|
|
4149
4241
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
4150
4242
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
4151
4243
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -4240,7 +4332,11 @@ var Calendar = class {
|
|
|
4240
4332
|
if (s.origin) {
|
|
4241
4333
|
this.draggingId = s.origin.id;
|
|
4242
4334
|
for (const el of this.elementsFor(s.origin.id)) {
|
|
4243
|
-
if (this.
|
|
4335
|
+
if (this.meta.get(el)?.variant === "timed" && s.mode === "move") {
|
|
4336
|
+
this.dragWidth = el.getBoundingClientRect().width;
|
|
4337
|
+
el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
4338
|
+
}
|
|
4339
|
+
if (this.opts.view !== "list" && !(this.current instanceof DayGridView) && s.mode === "move") {
|
|
4244
4340
|
const source = el.cloneNode(true);
|
|
4245
4341
|
source.classList.remove("is-selected");
|
|
4246
4342
|
source.classList.add("tnc-drag-source");
|
|
@@ -4293,24 +4389,96 @@ var Calendar = class {
|
|
|
4293
4389
|
const coordinate = axis === "x" ? pointer.clientX : pointer.clientY;
|
|
4294
4390
|
const lower = Math.max(0, axis === "x" ? viewport.left : viewport.top);
|
|
4295
4391
|
const upper = Math.min(axis === "x" ? this.win.innerWidth : this.win.innerHeight, axis === "x" ? viewport.right : viewport.bottom);
|
|
4296
|
-
if (coordinate < lower || coordinate > upper)
|
|
4392
|
+
if (coordinate < lower || coordinate > upper) {
|
|
4393
|
+
this.dragScrollEdges.delete(axis);
|
|
4394
|
+
continue;
|
|
4395
|
+
}
|
|
4297
4396
|
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)
|
|
4397
|
+
if (!delta) {
|
|
4398
|
+
this.dragScrollEdges.delete(axis);
|
|
4399
|
+
continue;
|
|
4400
|
+
}
|
|
4401
|
+
const edge = this.dragScrollEdges.get(axis);
|
|
4402
|
+
const direction = Math.sign(delta);
|
|
4403
|
+
const now = this.win.performance.now();
|
|
4404
|
+
if (!edge || edge.scroller !== scroller || edge.direction !== direction) {
|
|
4405
|
+
this.dragScrollEdges.set(axis, { scroller, direction, since: now });
|
|
4406
|
+
continue;
|
|
4407
|
+
}
|
|
4408
|
+
if (now - edge.since < 180) continue;
|
|
4299
4409
|
if (!this.dragScrollOrigins.has(scroller)) this.dragScrollOrigins.set(scroller, { top: scroller.scrollTop, left: scroller.scrollLeft });
|
|
4300
4410
|
const before = axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
|
|
4301
4411
|
if (axis === "x") scroller.scrollLeft += delta;
|
|
4302
4412
|
else scroller.scrollTop += delta;
|
|
4303
4413
|
moved ||= before !== (axis === "x" ? scroller.scrollLeft : scroller.scrollTop);
|
|
4304
4414
|
}
|
|
4305
|
-
}
|
|
4415
|
+
} else this.dragScrollEdges.clear();
|
|
4306
4416
|
if (moved) this.track(session, pointer);
|
|
4307
4417
|
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4308
4418
|
};
|
|
4309
4419
|
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
4310
4420
|
}
|
|
4421
|
+
onTouchStart = (ev) => {
|
|
4422
|
+
const first = ev.touches[0];
|
|
4423
|
+
const second = ev.touches[1];
|
|
4424
|
+
if (!first || !second) {
|
|
4425
|
+
this.endPinch();
|
|
4426
|
+
return;
|
|
4427
|
+
}
|
|
4428
|
+
const view = this.current;
|
|
4429
|
+
if (this.session?.active || !this.opts.pinchZoom || !(view instanceof TimeGridView)) return;
|
|
4430
|
+
const body = first.target.closest(".tnc-tg-body");
|
|
4431
|
+
if (!body?.contains(second.target)) return;
|
|
4432
|
+
this.endSession(null, true);
|
|
4433
|
+
if (this.pinch) return;
|
|
4434
|
+
const { top, height } = body.getBoundingClientRect();
|
|
4435
|
+
const y = (first.clientY + second.clientY) / 2;
|
|
4436
|
+
this.pinch = {
|
|
4437
|
+
body,
|
|
4438
|
+
distance: Math.max(1, Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY)),
|
|
4439
|
+
y,
|
|
4440
|
+
anchor: (y - top) / height,
|
|
4441
|
+
drawn: view.drawnSlotHeight,
|
|
4442
|
+
option: this.opts.slotHeight
|
|
4443
|
+
};
|
|
4444
|
+
};
|
|
4311
4445
|
onTouchMove = (ev) => {
|
|
4312
4446
|
if (this.session?.active) ev.preventDefault();
|
|
4447
|
+
const p = this.pinch;
|
|
4448
|
+
const first = ev.touches[0];
|
|
4449
|
+
const second = ev.touches[1];
|
|
4450
|
+
if (!p || !first || !second) return;
|
|
4451
|
+
if (!ev.cancelable) {
|
|
4452
|
+
this.endPinch();
|
|
4453
|
+
return;
|
|
4454
|
+
}
|
|
4455
|
+
ev.preventDefault();
|
|
4456
|
+
const height = p.drawn * Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY) / p.distance;
|
|
4457
|
+
p.y = (first.clientY + second.clientY) / 2;
|
|
4458
|
+
this.pinchTo(p, Math.min(Math.max(p.drawn, PINCH_MAX_SLOT), Math.max(Math.min(p.drawn, PINCH_MIN_SLOT), height)));
|
|
4313
4459
|
};
|
|
4460
|
+
onTouchEnd = (ev) => {
|
|
4461
|
+
if (ev.touches.length < 2) this.endPinch();
|
|
4462
|
+
};
|
|
4463
|
+
/** Rescales the grid, keeping the anchored time under the fingers. */
|
|
4464
|
+
pinchTo(p, slotHeight) {
|
|
4465
|
+
this.opts = { ...this.opts, slotHeight };
|
|
4466
|
+
if (!(this.current instanceof TimeGridView)) return;
|
|
4467
|
+
this.current.applyScale();
|
|
4468
|
+
const { top, height } = p.body.getBoundingClientRect();
|
|
4469
|
+
const scroller = scrollerOf(p.body, "y");
|
|
4470
|
+
if (scroller) scroller.scrollTop += top + height * p.anchor - p.y;
|
|
4471
|
+
}
|
|
4472
|
+
endPinch() {
|
|
4473
|
+
const p = this.pinch;
|
|
4474
|
+
this.pinch = null;
|
|
4475
|
+
if (!p?.body.isConnected) return;
|
|
4476
|
+
const settled = Math.round(this.opts.slotHeight);
|
|
4477
|
+
const slotHeight = settled === Math.round(p.drawn) ? p.option : settled;
|
|
4478
|
+
this.pinchTo(p, slotHeight);
|
|
4479
|
+
this.current?.render(null);
|
|
4480
|
+
if (slotHeight !== p.option) this.opts.onSlotHeightChange?.(slotHeight);
|
|
4481
|
+
}
|
|
4314
4482
|
onPointerMove = (ev) => {
|
|
4315
4483
|
const s = this.session;
|
|
4316
4484
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
@@ -4660,6 +4828,7 @@ var Calendar = class {
|
|
|
4660
4828
|
scroller.scrollLeft = position.left;
|
|
4661
4829
|
}
|
|
4662
4830
|
this.dragScrollOrigins.clear();
|
|
4831
|
+
this.dragScrollEdges.clear();
|
|
4663
4832
|
this.session = null;
|
|
4664
4833
|
this.listDragPreview?.element.remove();
|
|
4665
4834
|
this.listDragPreview = null;
|
|
@@ -4678,10 +4847,15 @@ var Calendar = class {
|
|
|
4678
4847
|
);
|
|
4679
4848
|
const draggedId = this.draggingId;
|
|
4680
4849
|
this.draggingId = null;
|
|
4850
|
+
this.dragWidth = null;
|
|
4681
4851
|
this.dragRow = null;
|
|
4682
|
-
if (draggedId) for (const el of this.elementsFor(draggedId))
|
|
4852
|
+
if (draggedId) for (const el of this.elementsFor(draggedId)) {
|
|
4853
|
+
el.classList.remove("is-dragging");
|
|
4854
|
+
el.style.removeProperty("--tnc-drag-width");
|
|
4855
|
+
}
|
|
4683
4856
|
this.strip?.markTarget(null);
|
|
4684
4857
|
if (!s.active) return;
|
|
4858
|
+
if (this.current instanceof DayGridView) this.current.render(null);
|
|
4685
4859
|
if (s.mode === "select") {
|
|
4686
4860
|
if (cancel) this.unselectSilently();
|
|
4687
4861
|
else if (this.selection) this.fireSelect(this.selection, ev);
|
|
@@ -5070,6 +5244,8 @@ var BASES_CALENDAR_DEFAULTS = {
|
|
|
5070
5244
|
slotDuration: "00:30:00",
|
|
5071
5245
|
snapDuration: "00:30:00",
|
|
5072
5246
|
scrollTime: "08:00:00",
|
|
5247
|
+
slotHeight: 24,
|
|
5248
|
+
pinchZoom: true,
|
|
5073
5249
|
nowIndicator: true,
|
|
5074
5250
|
showTimeGrid: true,
|
|
5075
5251
|
selectMirror: true,
|
|
@@ -5134,6 +5310,8 @@ function basesLayoutOptions(s, context = {}) {
|
|
|
5134
5310
|
slotDuration,
|
|
5135
5311
|
snapDuration: time("snapDuration") ?? slotDuration,
|
|
5136
5312
|
scrollTime: time("scrollTime", 48) ?? d.scrollTime,
|
|
5313
|
+
slotHeight: clamp(Math.round(num(s.slotHeight, d.slotHeight)), 5, 96),
|
|
5314
|
+
pinchZoom: pick("pinchZoom") !== false,
|
|
5137
5315
|
nowIndicator: pick("nowIndicator") !== false,
|
|
5138
5316
|
timeGrid: showTimeGrid,
|
|
5139
5317
|
selectMirror: pick("selectMirror") !== false,
|