@tasknotes/calendar 0.1.7 → 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 +32 -1
- package/README.md +7 -0
- package/dist/calendar.css +367 -44
- package/dist/cjs/index.cjs +478 -79
- package/dist/cjs/react.cjs +474 -79
- package/dist/esm/{chunk-EDRCVOY7.js → chunk-5PHSJK36.js} +474 -79
- package/dist/esm/index.js +5 -1
- package/dist/esm/react.js +1 -1
- package/dist/types/core/types.d.cts +1 -0
- package/dist/types/core/types.d.ts +1 -0
- package/dist/types/dom/calendar.d.cts +18 -1
- package/dist/types/dom/calendar.d.ts +18 -1
- 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 +8 -2
- package/dist/types/dom/options.d.ts +8 -2
- package/dist/types/dom/views/daygrid.d.cts +3 -1
- package/dist/types/dom/views/daygrid.d.ts +3 -1
- 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/dom/views/year.d.cts +1 -0
- package/dist/types/dom/views/year.d.ts +1 -0
- package/dist/types/tasknotes/bases.d.cts +2 -0
- package/dist/types/tasknotes/bases.d.ts +2 -0
- package/package.json +1 -1
|
@@ -31,14 +31,16 @@ import {
|
|
|
31
31
|
|
|
32
32
|
// src/dom/format.ts
|
|
33
33
|
var Formatter = class {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
hour12;
|
|
37
|
-
constructor(locale, hour12) {
|
|
34
|
+
constructor(locale, hour12, formatTime) {
|
|
35
|
+
this.formatTime = formatTime;
|
|
38
36
|
this.locale = locale;
|
|
39
37
|
const probe = new Intl.DateTimeFormat(locale, { hour: "numeric" }).resolvedOptions();
|
|
40
38
|
this.hour12 = hour12 ?? (probe.hour12 === true || probe.hourCycle === "h12" || probe.hourCycle === "h11");
|
|
41
39
|
}
|
|
40
|
+
formatTime;
|
|
41
|
+
cache = /* @__PURE__ */ new Map();
|
|
42
|
+
locale;
|
|
43
|
+
hour12;
|
|
42
44
|
fmt(key, opts) {
|
|
43
45
|
let f = this.cache.get(key);
|
|
44
46
|
if (!f) {
|
|
@@ -96,6 +98,7 @@ var Formatter = class {
|
|
|
96
98
|
/** Wall-clock minutes to a label. `compact` drops ":00" in 12-hour formats ("9am"). */
|
|
97
99
|
time(minute, compact = false) {
|
|
98
100
|
const m = (Math.round(minute) % 1440 + 1440) % 1440;
|
|
101
|
+
if (this.formatTime) return this.formatTime(m);
|
|
99
102
|
const date = new Date(Date.UTC(2e3, 0, 1, Math.floor(m / 60), m % 60));
|
|
100
103
|
if (this.hour12) {
|
|
101
104
|
const parts = this.fmt("t12", { hour: "numeric", minute: "2-digit", hour12: true }).formatToParts(date);
|
|
@@ -187,6 +190,7 @@ function defaultOptions() {
|
|
|
187
190
|
snapDuration: void 0,
|
|
188
191
|
scrollTime: "08:00",
|
|
189
192
|
slotHeight: 24,
|
|
193
|
+
pinchZoom: true,
|
|
190
194
|
expandRows: false,
|
|
191
195
|
nowIndicator: true,
|
|
192
196
|
selectMirror: true,
|
|
@@ -228,6 +232,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
228
232
|
"locale",
|
|
229
233
|
"firstDay",
|
|
230
234
|
"hour12",
|
|
235
|
+
"formatTime",
|
|
231
236
|
"weekNumbers",
|
|
232
237
|
"weekends",
|
|
233
238
|
"hiddenDays",
|
|
@@ -240,9 +245,11 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
240
245
|
"slotDuration",
|
|
241
246
|
"snapDuration",
|
|
242
247
|
"slotHeight",
|
|
248
|
+
"pinchZoom",
|
|
243
249
|
"expandRows",
|
|
244
250
|
"nowIndicator",
|
|
245
251
|
"slotEventOverlap",
|
|
252
|
+
"overlapStackThreshold",
|
|
246
253
|
"eventMinHeight",
|
|
247
254
|
"eventMaxStack",
|
|
248
255
|
"dayMaxEvents",
|
|
@@ -359,8 +366,9 @@ var DayGridView = class {
|
|
|
359
366
|
constructor(ctx) {
|
|
360
367
|
this.ctx = ctx;
|
|
361
368
|
const { doc, opts, days } = ctx;
|
|
362
|
-
this.dotsMode = opts.monthDisplay === "dots";
|
|
369
|
+
this.dotsMode = opts.monthDisplay === "dots" || opts.monthDisplay === "chips";
|
|
363
370
|
this.el = h(doc, "div", this.dotsMode ? "tnc-dg tnc-dg--dots" : "tnc-dg");
|
|
371
|
+
this.el.classList.toggle("tnc-dg--chips", opts.monthDisplay === "chips");
|
|
364
372
|
this.scroller = h(doc, "div", "tnc-dg-scroll");
|
|
365
373
|
this.grid = h(doc, "div", "tnc-dg-grid", { role: "grid", "aria-label": ctx.view.title });
|
|
366
374
|
const perWeek = this.daysPerWeek();
|
|
@@ -390,14 +398,14 @@ var DayGridView = class {
|
|
|
390
398
|
const cell = h(doc, "div", cls.join(" "), { role: "gridcell", "aria-label": ctx.fmt.longDate(d), "data-date": arg.dateStr });
|
|
391
399
|
if (this.dotsMode) cell.tabIndex = 0;
|
|
392
400
|
const num = ctx.fmt.dayNumber(d);
|
|
393
|
-
const label = ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num}` : num;
|
|
401
|
+
const label = !this.dotsMode && ymdOfDay(d).day === 1 ? `${ctx.fmt.monthShort(d)} ${num}` : num;
|
|
394
402
|
cell.append(
|
|
395
403
|
h(
|
|
396
404
|
doc,
|
|
397
405
|
"div",
|
|
398
406
|
"tnc-dg-top",
|
|
399
407
|
null,
|
|
400
|
-
opts.navLinks ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
408
|
+
opts.navLinks && !this.dotsMode ? ctx.navLink(d, label, "tnc-dg-num") : h(doc, "span", "tnc-dg-num", null, label)
|
|
401
409
|
)
|
|
402
410
|
);
|
|
403
411
|
dates.append(cell);
|
|
@@ -411,20 +419,20 @@ var DayGridView = class {
|
|
|
411
419
|
layer.append(content);
|
|
412
420
|
row.append(dates, layer);
|
|
413
421
|
this.grid.append(row);
|
|
414
|
-
const dots =
|
|
422
|
+
const dots = cells.map((cell) => {
|
|
415
423
|
const box = h(doc, "div", "tnc-dg-dots", { "aria-hidden": "true" });
|
|
416
424
|
if (opts.dayMaxEvents === false) box.classList.add("is-unlimited");
|
|
417
425
|
const count = h(doc, "span", "tnc-dg-dots-more");
|
|
418
426
|
cell.append(box);
|
|
419
427
|
return { box, pool: new ItemPool(ctx, box), count };
|
|
420
|
-
})
|
|
428
|
+
});
|
|
421
429
|
this.weeks.push({ days: wdays, row, cells, layer, sel, more, pool: new ItemPool(ctx, content), dots });
|
|
422
430
|
}
|
|
423
431
|
this.scroller.append(this.grid);
|
|
424
432
|
this.el.append(this.scroller);
|
|
425
433
|
this.el.classList.toggle("tnc-dg--auto", opts.height === "auto");
|
|
426
434
|
const RO = ctx.win.ResizeObserver;
|
|
427
|
-
if (RO
|
|
435
|
+
if (RO) {
|
|
428
436
|
this.resizeObs = new RO(() => {
|
|
429
437
|
if (this.sizeKey() !== this.lastHeights) this.render(null);
|
|
430
438
|
});
|
|
@@ -442,6 +450,7 @@ var DayGridView = class {
|
|
|
442
450
|
topPx = 24;
|
|
443
451
|
lastHeights = "";
|
|
444
452
|
dotsMode;
|
|
453
|
+
dragPreviews = [];
|
|
445
454
|
daysPerWeek() {
|
|
446
455
|
const { days } = this.ctx;
|
|
447
456
|
const first = days[0];
|
|
@@ -455,16 +464,71 @@ var DayGridView = class {
|
|
|
455
464
|
}
|
|
456
465
|
render(_change) {
|
|
457
466
|
const { ctx } = this;
|
|
467
|
+
this.clearDragPreviews();
|
|
468
|
+
const dragged = ctx.draggingId ? ctx.store.get(ctx.draggingId) : void 0;
|
|
469
|
+
const source = dragged ? this.elementsFor(dragged.id)[0] : void 0;
|
|
470
|
+
if (dragged && source && this.el.closest(".tnc-dragging-move")) {
|
|
471
|
+
const proposals = [dragged, ...[...ctx.store.all()].filter((item) => item.id !== dragged.id && item.item.preview)];
|
|
472
|
+
for (const proposal of proposals) {
|
|
473
|
+
const original = this.elementsFor(proposal.id)[0] ?? source;
|
|
474
|
+
const bounds = original.getBoundingClientRect();
|
|
475
|
+
const sourceWeek = original.closest(".tnc-dg-week");
|
|
476
|
+
const offset = bounds.top - sourceWeek.getBoundingClientRect().top;
|
|
477
|
+
for (const week of this.weeks) {
|
|
478
|
+
const start = week.days.findIndex((day) => day >= proposal.startDay && day < proposal.endDay);
|
|
479
|
+
if (start < 0) continue;
|
|
480
|
+
let end = start + 1;
|
|
481
|
+
while (!this.dotsMode && end < week.days.length && week.days[end] < proposal.endDay) end++;
|
|
482
|
+
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 });
|
|
483
|
+
preview.classList.add("tnc-month-preview");
|
|
484
|
+
preview.inert = true;
|
|
485
|
+
setStyle(preview, {
|
|
486
|
+
left: `calc(${pct(start / week.days.length)} + 2px)`,
|
|
487
|
+
width: `calc(${pct((end - start) / week.days.length)} - 4px)`,
|
|
488
|
+
top: `${Math.max(this.topPx, Math.min(offset, week.row.clientHeight - bounds.height - 4))}px`,
|
|
489
|
+
height: `${bounds.height}px`
|
|
490
|
+
});
|
|
491
|
+
week.layer.append(preview);
|
|
492
|
+
this.dragPreviews.push(preview);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
this.dotsMode = ctx.opts.monthDisplay !== "full" || this.grid.clientWidth / this.daysPerWeek() < 72;
|
|
498
|
+
this.el.classList.toggle("tnc-dg--dots", this.dotsMode);
|
|
499
|
+
this.el.classList.toggle("tnc-dg--compact", this.dotsMode && ctx.opts.monthDisplay === "full");
|
|
500
|
+
this.el.classList.toggle("tnc-dg--chips", ctx.opts.monthDisplay === "chips" && this.weeks[0].dots[0].box.clientHeight >= 44);
|
|
458
501
|
const cs = ctx.win.getComputedStyle(this.el);
|
|
459
502
|
this.barPx = px(cs, "--tnc-bar-h", 20);
|
|
460
503
|
this.topPx = this.weeks[0]?.cells[0]?.querySelector(".tnc-dg-top")?.offsetHeight || px(cs, "--tnc-dg-top-h", 24);
|
|
461
504
|
this.morePx = this.measureMore(cs);
|
|
462
505
|
this.lastHeights = this.sizeKey();
|
|
463
506
|
for (const w of this.weeks) {
|
|
464
|
-
|
|
465
|
-
|
|
507
|
+
for (const cell of w.cells) {
|
|
508
|
+
if (this.dotsMode) cell.tabIndex = 0;
|
|
509
|
+
else cell.removeAttribute("tabindex");
|
|
510
|
+
if (this.dotsMode && ctx.opts.monthDisplay === "full") cell.setAttribute("aria-haspopup", "dialog");
|
|
511
|
+
else cell.removeAttribute("aria-haspopup");
|
|
512
|
+
}
|
|
513
|
+
for (const cell of w.dots) cell.box.hidden = !this.dotsMode;
|
|
514
|
+
if (this.dotsMode) {
|
|
515
|
+
w.pool.begin();
|
|
516
|
+
w.pool.end();
|
|
517
|
+
w.more.replaceChildren();
|
|
518
|
+
this.renderDots(w);
|
|
519
|
+
} else {
|
|
520
|
+
for (const cell of w.dots) {
|
|
521
|
+
cell.pool.begin();
|
|
522
|
+
cell.pool.end();
|
|
523
|
+
}
|
|
524
|
+
this.renderWeek(w);
|
|
525
|
+
}
|
|
466
526
|
}
|
|
467
527
|
}
|
|
528
|
+
clearDragPreviews() {
|
|
529
|
+
for (const preview of this.dragPreviews) this.ctx.destroyItemEl(preview);
|
|
530
|
+
this.dragPreviews = [];
|
|
531
|
+
}
|
|
468
532
|
/** Height of a "+N more" link as the host styles it (`--tnc-more-h`, or a taller min-height). */
|
|
469
533
|
measureMore(cs) {
|
|
470
534
|
const fallback = px(cs, "--tnc-more-h", this.barPx);
|
|
@@ -561,7 +625,8 @@ var DayGridView = class {
|
|
|
561
625
|
w.more.append(link);
|
|
562
626
|
}
|
|
563
627
|
existing.delete(key);
|
|
564
|
-
link.
|
|
628
|
+
link.setAttribute("aria-label", ctx.text.more(items.length));
|
|
629
|
+
link.textContent = w.row.clientWidth / n < 72 ? `+${new Intl.NumberFormat(ctx.opts.locale).format(items.length)}` : ctx.text.more(items.length);
|
|
565
630
|
setStyle(link, {
|
|
566
631
|
left: `calc(${pct(col / n)} + 2px)`,
|
|
567
632
|
width: `calc(${pct(1 / n)} - 4px)`,
|
|
@@ -591,10 +656,11 @@ var DayGridView = class {
|
|
|
591
656
|
const { dayMaxEvents } = ctx.opts;
|
|
592
657
|
const first = w.dots[0].box;
|
|
593
658
|
const cs = ctx.win.getComputedStyle(first);
|
|
594
|
-
const
|
|
659
|
+
const chips = this.el.classList.contains("tnc-dg--chips");
|
|
660
|
+
const size = chips ? px(cs, "--tnc-chip-h", 14) : px(cs, "--tnc-dot-size", 8);
|
|
595
661
|
const gap = px(cs, "--tnc-dot-gap", 3);
|
|
596
662
|
const step = size + gap;
|
|
597
|
-
const perRow = Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
663
|
+
const perRow = chips ? 1 : Math.max(1, Math.floor((first.clientWidth + gap) / step));
|
|
598
664
|
const rows = Math.max(1, Math.floor((first.clientHeight + gap) / step));
|
|
599
665
|
const fits = dayMaxEvents === false ? Infinity : perRow * rows;
|
|
600
666
|
const cap = typeof dayMaxEvents === "number" && dayMaxEvents > 0 ? Math.min(dayMaxEvents, fits) : fits;
|
|
@@ -603,7 +669,7 @@ var DayGridView = class {
|
|
|
603
669
|
w.days.forEach((day, i) => {
|
|
604
670
|
const cell = w.dots[i];
|
|
605
671
|
const items = this.itemsOn(day);
|
|
606
|
-
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(
|
|
672
|
+
const shown = items.length <= cap ? items.length : cap >= fits ? Math.max(1, cap - (chips ? 1 : 2)) : Math.max(1, cap);
|
|
607
673
|
cell.pool.begin();
|
|
608
674
|
const els = items.slice(0, shown).map((n) => cell.pool.place(n.id, { n, day, isStart: n.startDay >= day, isEnd: n.endDay <= day + 1, variant: "indicator", mirror: false }));
|
|
609
675
|
cell.pool.end();
|
|
@@ -654,6 +720,7 @@ var DayGridView = class {
|
|
|
654
720
|
return this.weeks.flatMap((w) => [...w.pool.elementsFor(id), ...(w.dots ?? []).flatMap((c) => c.pool.elementsFor(id))]);
|
|
655
721
|
}
|
|
656
722
|
destroy() {
|
|
723
|
+
this.clearDragPreviews();
|
|
657
724
|
this.resizeObs?.disconnect();
|
|
658
725
|
for (const w of this.weeks) {
|
|
659
726
|
w.pool.clear();
|
|
@@ -706,6 +773,7 @@ var ListView = class {
|
|
|
706
773
|
frame = null;
|
|
707
774
|
/** A picked day stays marked until the list is scrolled by hand. */
|
|
708
775
|
pinned = false;
|
|
776
|
+
pinnedTop = 0;
|
|
709
777
|
itemsOn(day) {
|
|
710
778
|
const { store, opts } = this.ctx;
|
|
711
779
|
const out = store.spanningIn(day, day + 1, true);
|
|
@@ -720,6 +788,7 @@ var ListView = class {
|
|
|
720
788
|
const leadItems = [];
|
|
721
789
|
const inLead = /* @__PURE__ */ new Set();
|
|
722
790
|
const keep = /* @__PURE__ */ new Set();
|
|
791
|
+
let itemCount = 0;
|
|
723
792
|
let prev = null;
|
|
724
793
|
for (const day of days) {
|
|
725
794
|
let items = this.itemsOn(day);
|
|
@@ -733,7 +802,7 @@ var ListView = class {
|
|
|
733
802
|
return false;
|
|
734
803
|
});
|
|
735
804
|
}
|
|
736
|
-
if (!items.length && !agenda) continue;
|
|
805
|
+
if (!items.length && !agenda && opts.listDayCount !== 1) continue;
|
|
737
806
|
keep.add(day);
|
|
738
807
|
let section = this.sections.get(day);
|
|
739
808
|
let pool = this.pools.get(day);
|
|
@@ -752,6 +821,7 @@ var ListView = class {
|
|
|
752
821
|
this.pools.set(day, pool);
|
|
753
822
|
}
|
|
754
823
|
section.classList.toggle("is-empty", !items.length);
|
|
824
|
+
itemCount += items.length;
|
|
755
825
|
this.fill(pool, items, () => day);
|
|
756
826
|
if (prev ? prev.nextSibling !== section : this.body.firstChild !== section) {
|
|
757
827
|
if (prev) prev.after(section);
|
|
@@ -767,13 +837,14 @@ var ListView = class {
|
|
|
767
837
|
this.sections.delete(day);
|
|
768
838
|
}
|
|
769
839
|
this.renderLead(leadItems);
|
|
770
|
-
let empty = this.body.querySelector("
|
|
771
|
-
if (!
|
|
840
|
+
let empty = this.body.querySelector(".tnc-list-empty");
|
|
841
|
+
if (!itemCount && !leadItems.length && (!agenda || opts.listEmptyContent)) {
|
|
772
842
|
if (!empty) {
|
|
773
843
|
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
774
844
|
const content = ctx.opts.listEmptyContent?.();
|
|
775
845
|
if (content) empty.replaceChildren(content);
|
|
776
|
-
this.
|
|
846
|
+
if (this.sections.size === 1) this.sections.values().next().value.append(empty);
|
|
847
|
+
else this.body.prepend(empty);
|
|
777
848
|
}
|
|
778
849
|
} else empty?.remove();
|
|
779
850
|
this.ctx.strip?.draw();
|
|
@@ -831,9 +902,10 @@ var ListView = class {
|
|
|
831
902
|
const section = day === this.ctx.days[0] && this.lead ? this.lead.section : this.sections.get(day);
|
|
832
903
|
if (!section) return;
|
|
833
904
|
this.ctx.strip?.measure();
|
|
834
|
-
section.
|
|
905
|
+
this.scroller.scrollTop += section.getBoundingClientRect().top - this.scroller.getBoundingClientRect().top;
|
|
835
906
|
this.ctx.strip?.setFocus(day);
|
|
836
907
|
this.pinned = true;
|
|
908
|
+
this.pinnedTop = this.scroller.scrollTop;
|
|
837
909
|
}
|
|
838
910
|
fold = () => {
|
|
839
911
|
this.pinned = false;
|
|
@@ -841,6 +913,7 @@ var ListView = class {
|
|
|
841
913
|
if (strip?.expanded) strip.setExpanded(false);
|
|
842
914
|
};
|
|
843
915
|
onScroll = () => {
|
|
916
|
+
if (this.pinned && Math.abs(this.scroller.scrollTop - this.pinnedTop) > 1) this.pinned = false;
|
|
844
917
|
if (this.frame !== null) return;
|
|
845
918
|
this.frame = this.ctx.win.requestAnimationFrame(() => {
|
|
846
919
|
this.frame = null;
|
|
@@ -851,7 +924,7 @@ var ListView = class {
|
|
|
851
924
|
followTop() {
|
|
852
925
|
const strip = this.ctx.strip;
|
|
853
926
|
if (!strip || this.pinned) return;
|
|
854
|
-
const edge = strip.el.getBoundingClientRect().bottom + 1;
|
|
927
|
+
const edge = Math.max(strip.el.getBoundingClientRect().bottom, this.scroller.getBoundingClientRect().top) + 1;
|
|
855
928
|
for (const day of this.ctx.days) {
|
|
856
929
|
const section = this.sections.get(day);
|
|
857
930
|
if (section && section.getBoundingClientRect().bottom > edge) {
|
|
@@ -899,10 +972,11 @@ var ListView = class {
|
|
|
899
972
|
// src/dom/views/strip.ts
|
|
900
973
|
var SWIPE_PX = 40;
|
|
901
974
|
var DateStrip = class {
|
|
902
|
-
constructor(ctx, pick, focus, onShown) {
|
|
975
|
+
constructor(ctx, pick, focus, onShown, onPage) {
|
|
903
976
|
this.ctx = ctx;
|
|
904
977
|
this.pick = pick;
|
|
905
978
|
this.onShown = onShown;
|
|
979
|
+
this.onPage = onPage;
|
|
906
980
|
const { doc, text } = ctx;
|
|
907
981
|
this.focus = focus;
|
|
908
982
|
this.shown = startOfWeek(focus, ctx.firstDay);
|
|
@@ -920,13 +994,16 @@ var DateStrip = class {
|
|
|
920
994
|
head.append(this.title, this.todayBtn, prev, next, this.slot);
|
|
921
995
|
this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
|
|
922
996
|
this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
|
|
923
|
-
const handle = h(doc, "
|
|
924
|
-
handle.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
997
|
+
const handle = h(doc, "div", "tnc-strip-handle", { "aria-hidden": "true" });
|
|
925
998
|
this.el.append(head, this.dows, this.grid, handle);
|
|
926
999
|
this.grid.addEventListener("click", this.onClick);
|
|
927
1000
|
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
928
1001
|
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
929
1002
|
this.el.addEventListener("pointerup", this.onPointerUp);
|
|
1003
|
+
this.el.addEventListener("touchstart", this.ownTouch, { passive: true });
|
|
1004
|
+
this.el.addEventListener("touchmove", this.ownTouch, { passive: true });
|
|
1005
|
+
this.el.addEventListener("touchend", this.ownTouch, { passive: true });
|
|
1006
|
+
this.el.addEventListener("touchcancel", this.ownTouch, { passive: true });
|
|
930
1007
|
this.el.addEventListener("pointercancel", this.onPointerCancel);
|
|
931
1008
|
this.el.addEventListener("click", this.onClickCapture, true);
|
|
932
1009
|
this.draw();
|
|
@@ -935,6 +1012,7 @@ var DateStrip = class {
|
|
|
935
1012
|
ctx;
|
|
936
1013
|
pick;
|
|
937
1014
|
onShown;
|
|
1015
|
+
onPage;
|
|
938
1016
|
el;
|
|
939
1017
|
title;
|
|
940
1018
|
dows;
|
|
@@ -993,6 +1071,7 @@ var DateStrip = class {
|
|
|
993
1071
|
step(dir) {
|
|
994
1072
|
this.shown = this.expanded ? addMonths(this.shown, dir) : this.shown + 7 * dir;
|
|
995
1073
|
this.draw();
|
|
1074
|
+
this.onPage?.(this.expanded ? this.shown : this.focus + 7 * dir);
|
|
996
1075
|
}
|
|
997
1076
|
days() {
|
|
998
1077
|
if (!this.expanded) return Array.from({ length: 7 }, (_, i) => this.shown + i);
|
|
@@ -1017,7 +1096,8 @@ var DateStrip = class {
|
|
|
1017
1096
|
const tabDay = days.includes(this.focus) ? this.focus : days[0];
|
|
1018
1097
|
const cells = days.map((d) => {
|
|
1019
1098
|
const arg = ctx.dayArg(d);
|
|
1020
|
-
const
|
|
1099
|
+
const items = store.inRange(d, d + 1).filter((n) => n.item.display !== "background" && n.item.display !== "none");
|
|
1100
|
+
const count = items.length;
|
|
1021
1101
|
const first = ymdOfDay(d).day === 1 && d !== days[0] && !this.expanded;
|
|
1022
1102
|
const label = fmt.longDate(d);
|
|
1023
1103
|
const cell = h(
|
|
@@ -1041,7 +1121,15 @@ var DateStrip = class {
|
|
|
1041
1121
|
}
|
|
1042
1122
|
);
|
|
1043
1123
|
if (first) cell.append(h(doc, "span", "tnc-strip-month", { "aria-hidden": "true" }, fmt.monthShort(d)));
|
|
1044
|
-
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d))
|
|
1124
|
+
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d)));
|
|
1125
|
+
const dots = h(doc, "span", "tnc-strip-dots", { "aria-hidden": "true" });
|
|
1126
|
+
for (const item of items.slice(0, 3)) {
|
|
1127
|
+
const dot = h(doc, "span", "tnc-strip-dot");
|
|
1128
|
+
const color = item.item.color || item.item.borderColor || item.item.backgroundColor;
|
|
1129
|
+
if (color) dot.style.backgroundColor = color;
|
|
1130
|
+
dots.append(dot);
|
|
1131
|
+
}
|
|
1132
|
+
cell.append(dots);
|
|
1045
1133
|
return cell;
|
|
1046
1134
|
});
|
|
1047
1135
|
this.grid.replaceChildren(...cells);
|
|
@@ -1081,6 +1169,9 @@ var DateStrip = class {
|
|
|
1081
1169
|
if (ev.button !== 0 || this.ctx.draggingId) return;
|
|
1082
1170
|
this.swipe = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
|
|
1083
1171
|
};
|
|
1172
|
+
ownTouch = (event) => {
|
|
1173
|
+
event.stopPropagation();
|
|
1174
|
+
};
|
|
1084
1175
|
onPointerUp = (ev) => {
|
|
1085
1176
|
const s = this.swipe;
|
|
1086
1177
|
this.swipe = null;
|
|
@@ -1120,6 +1211,7 @@ var TimeGridView = class {
|
|
|
1120
1211
|
const showGrid = opts.timeGrid;
|
|
1121
1212
|
this.el = h(doc, "div", "tnc-tg", { role: "table", "aria-label": ctx.view.title });
|
|
1122
1213
|
this.el.classList.toggle("tnc-tg--no-grid", !showGrid);
|
|
1214
|
+
this.el.classList.toggle("tnc-tg--pinch", showGrid && opts.pinchZoom);
|
|
1123
1215
|
this.scroller = h(doc, "div", "tnc-tg-scroll");
|
|
1124
1216
|
this.dayNavigation = h(doc, "div", "tnc-tg-day-navigation", { role: "group", "aria-label": ctx.view.title });
|
|
1125
1217
|
this.scroller.addEventListener("scroll", () => this.updateDayNavigation());
|
|
@@ -1269,6 +1361,11 @@ var TimeGridView = class {
|
|
|
1269
1361
|
}
|
|
1270
1362
|
if (this.nowGutter) gutter.append(this.nowGutter);
|
|
1271
1363
|
}
|
|
1364
|
+
/** Slot height as drawn, which `expandRows` can stretch past the option. */
|
|
1365
|
+
get drawnSlotHeight() {
|
|
1366
|
+
return this.ctx.slot.duration * this.ppm;
|
|
1367
|
+
}
|
|
1368
|
+
/** Applies `slotHeight`. Items are placed in percentages, so they follow without a render. */
|
|
1272
1369
|
applyScale() {
|
|
1273
1370
|
const { opts, slot } = this.ctx;
|
|
1274
1371
|
this.ppm = opts.slotHeight / slot.duration;
|
|
@@ -1449,15 +1546,17 @@ var TimeGridView = class {
|
|
|
1449
1546
|
const { opts } = ctx;
|
|
1450
1547
|
const readableWidth = Number.parseFloat(ctx.win.getComputedStyle(this.el).getPropertyValue("--tnc-event-min-width")) || 128;
|
|
1451
1548
|
const laneWidth = c.col.clientWidth * (lane === "actual" && placements.some((placement) => placement.split) ? 0.22 : 1);
|
|
1452
|
-
const
|
|
1549
|
+
const overlapThreshold = opts.overlapStackThreshold ?? 0;
|
|
1550
|
+
const collapsedClusters = new Set(placements.filter((placement) => {
|
|
1453
1551
|
const columns = opts.eventMaxStack > 0 ? Math.min(placement.cols, opts.eventMaxStack) : placement.cols;
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
1552
|
+
const denseOverlap = overlapThreshold > 1 && placement.cols >= overlapThreshold;
|
|
1553
|
+
return columns > 1 && (columns * readableWidth > laneWidth || denseOverlap);
|
|
1554
|
+
}).map((placement) => placement.cluster));
|
|
1458
1555
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
1459
1556
|
pool.begin();
|
|
1460
1557
|
for (const p of placements) {
|
|
1558
|
+
const withMore = lane === "plan" || collapsedClusters.has(p.cluster);
|
|
1559
|
+
const maxStack = collapsedClusters.has(p.cluster) ? 1 : opts.eventMaxStack;
|
|
1461
1560
|
const preview = p.item.item.preview || p.item.id === ctx.draggingId;
|
|
1462
1561
|
if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
|
|
1463
1562
|
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, split: p.split, items: [] };
|
|
@@ -1470,7 +1569,7 @@ var TimeGridView = class {
|
|
|
1470
1569
|
}
|
|
1471
1570
|
const cols = !preview && withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
|
|
1472
1571
|
const span = Math.min(p.span, cols - p.col);
|
|
1473
|
-
const left = p.col / cols;
|
|
1572
|
+
const left = p.item.id === ctx.draggingId ? 0 : p.col / cols;
|
|
1474
1573
|
let width = span / cols;
|
|
1475
1574
|
if (opts.slotEventOverlap && cols > 1 && p.col + span < cols) width = Math.min(1 - left, width * 1.7);
|
|
1476
1575
|
const el = pool.place(p.item.id, this.meta(p, false));
|
|
@@ -1485,8 +1584,9 @@ var TimeGridView = class {
|
|
|
1485
1584
|
});
|
|
1486
1585
|
}
|
|
1487
1586
|
pool.end();
|
|
1488
|
-
if (!withMore) return;
|
|
1489
1587
|
for (const [cluster, rec] of hiddenByCluster) {
|
|
1588
|
+
const collapse = collapsedClusters.has(cluster);
|
|
1589
|
+
const maxStack = collapse ? 1 : opts.eventMaxStack;
|
|
1490
1590
|
const key = `${lane}-${cluster}`;
|
|
1491
1591
|
let link = c.more.querySelector(`[data-cluster="${key}"]`);
|
|
1492
1592
|
if (!link) {
|
|
@@ -1500,8 +1600,15 @@ var TimeGridView = class {
|
|
|
1500
1600
|
link.classList.toggle("tnc-more--collision", collapse);
|
|
1501
1601
|
link.textContent = `+${rec.count}`;
|
|
1502
1602
|
if (collapse) {
|
|
1503
|
-
|
|
1504
|
-
|
|
1603
|
+
const items = placements.filter((placement) => placement.cluster === cluster).map((placement) => placement.item.item.title);
|
|
1604
|
+
const count = ctx.text.appointments(rec.count);
|
|
1605
|
+
const interval = `${ctx.fmt.time(rec.start)} \u2013 ${ctx.fmt.time(rec.end)}`;
|
|
1606
|
+
link.replaceChildren(
|
|
1607
|
+
h(ctx.doc, "span", "tnc-collision-count", null, String(rec.count)),
|
|
1608
|
+
h(ctx.doc, "span", "tnc-collision-range", null, interval),
|
|
1609
|
+
h(ctx.doc, "span", "tnc-collision-title", { "aria-hidden": "true" }, items.join(" \xB7 "))
|
|
1610
|
+
);
|
|
1611
|
+
link.setAttribute("aria-label", `${count}, ${interval}: ${items.join(", ")}`);
|
|
1505
1612
|
link.title = link.getAttribute("aria-label");
|
|
1506
1613
|
} else {
|
|
1507
1614
|
link.removeAttribute("aria-label");
|
|
@@ -1510,6 +1617,7 @@ var TimeGridView = class {
|
|
|
1510
1617
|
const cols = maxStack;
|
|
1511
1618
|
setStyle(link, {
|
|
1512
1619
|
top: top(rec.start),
|
|
1620
|
+
height: collapse ? pct((rec.end - rec.start) / this.minutes) : "",
|
|
1513
1621
|
...inLane(lane, rec.split, (cols - 1) / cols, 1 / cols)
|
|
1514
1622
|
});
|
|
1515
1623
|
}
|
|
@@ -1576,12 +1684,10 @@ var TimeGridView = class {
|
|
|
1576
1684
|
const r2 = cells[i2].getBoundingClientRect();
|
|
1577
1685
|
if (x >= r2.left && x < r2.right) return i2;
|
|
1578
1686
|
}
|
|
1579
|
-
const firstR = cells[0]?.getBoundingClientRect();
|
|
1580
|
-
const lastR = cells[cells.length - 1]?.getBoundingClientRect();
|
|
1581
|
-
if (firstR && x < firstR.left) return 0;
|
|
1582
|
-
if (lastR && x >= lastR.right) return cells.length - 1;
|
|
1583
1687
|
return -1;
|
|
1584
1688
|
};
|
|
1689
|
+
const bounds = this.scroller.getBoundingClientRect();
|
|
1690
|
+
if (x < bounds.left || x >= bounds.right || y < bounds.top || y >= bounds.bottom) return null;
|
|
1585
1691
|
if (this.allDayRow) {
|
|
1586
1692
|
const r2 = this.allDayRow.getBoundingClientRect();
|
|
1587
1693
|
if (y >= r2.top && y < r2.bottom || !this.body && y >= r2.top) {
|
|
@@ -1646,7 +1752,9 @@ var TimeGridView = class {
|
|
|
1646
1752
|
scrollToTime(minute) {
|
|
1647
1753
|
if (!this.body) return;
|
|
1648
1754
|
const y = (minute - this.ctx.slot.min) * this.ppm;
|
|
1649
|
-
|
|
1755
|
+
const label = this.gutter?.querySelector(".tnc-slot-label");
|
|
1756
|
+
const inset = label ? Math.ceil(label.getBoundingClientRect().height / 2) + 2 : 0;
|
|
1757
|
+
this.scroller.scrollTop = Math.max(0, y - inset);
|
|
1650
1758
|
}
|
|
1651
1759
|
getScroll() {
|
|
1652
1760
|
return { top: this.scroller.scrollTop, left: this.scroller.scrollLeft };
|
|
@@ -2304,6 +2412,14 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2304
2412
|
setScroll(s) {
|
|
2305
2413
|
this.scroller.scrollTop = s.top;
|
|
2306
2414
|
}
|
|
2415
|
+
revealDay(day) {
|
|
2416
|
+
const month = this.cells.find((cell) => cell.day === day)?.el.closest(".tnc-ym");
|
|
2417
|
+
if (!month) return;
|
|
2418
|
+
const bounds = month.getBoundingClientRect();
|
|
2419
|
+
const viewport = this.scroller.getBoundingClientRect();
|
|
2420
|
+
if (bounds.top < viewport.top) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
2421
|
+
else if (bounds.bottom > viewport.bottom) this.scroller.scrollTop += bounds.top - viewport.top;
|
|
2422
|
+
}
|
|
2307
2423
|
elementsFor(_id) {
|
|
2308
2424
|
return [];
|
|
2309
2425
|
}
|
|
@@ -2316,6 +2432,8 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
2316
2432
|
var EXTERNAL_ID = "__tnc_external__";
|
|
2317
2433
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
2318
2434
|
var MAX_AGENDA_DAYS = 366;
|
|
2435
|
+
var PINCH_MIN_SLOT = 16;
|
|
2436
|
+
var PINCH_MAX_SLOT = 96;
|
|
2319
2437
|
function sameOption(a, b, depth = 0) {
|
|
2320
2438
|
if (a === b) return true;
|
|
2321
2439
|
if (typeof a === "function" && typeof b === "function") return true;
|
|
@@ -2343,7 +2461,7 @@ var Calendar = class {
|
|
|
2343
2461
|
this.win = this.doc.defaultView ?? globalThis;
|
|
2344
2462
|
this.opts = { ...defaultOptions(), ...options };
|
|
2345
2463
|
this.zone = getTimeZone(this.opts.timeZone);
|
|
2346
|
-
this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
2464
|
+
this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
2347
2465
|
this.text = this.resolveText();
|
|
2348
2466
|
this.order = parseOrder(this.opts.eventOrder);
|
|
2349
2467
|
this.store = new ItemStore(this.zone, {
|
|
@@ -2383,10 +2501,18 @@ var Calendar = class {
|
|
|
2383
2501
|
conflicts = /* @__PURE__ */ new Map();
|
|
2384
2502
|
slot = { min: 0, max: MINUTES_PER_DAY, duration: 30, snap: 30, scroll: 480 };
|
|
2385
2503
|
draggingId = null;
|
|
2504
|
+
listDragPreview = null;
|
|
2505
|
+
dragSources = [];
|
|
2506
|
+
dragWidth = null;
|
|
2507
|
+
dragScrollFrame = null;
|
|
2508
|
+
dragScrollOrigins = /* @__PURE__ */ new Map();
|
|
2509
|
+
dragScrollEdges = /* @__PURE__ */ new Map();
|
|
2510
|
+
itemWriteStates = /* @__PURE__ */ new Map();
|
|
2386
2511
|
dragRow = null;
|
|
2387
2512
|
strip = null;
|
|
2388
2513
|
stripSize = null;
|
|
2389
2514
|
rebuilding = false;
|
|
2515
|
+
pinch = null;
|
|
2390
2516
|
toolbarEl;
|
|
2391
2517
|
viewHost;
|
|
2392
2518
|
current = null;
|
|
@@ -2396,6 +2522,7 @@ var Calendar = class {
|
|
|
2396
2522
|
session = null;
|
|
2397
2523
|
selection = null;
|
|
2398
2524
|
suppressClick = false;
|
|
2525
|
+
cancelledPointerId = null;
|
|
2399
2526
|
selectedIds = /* @__PURE__ */ new Set();
|
|
2400
2527
|
popover = null;
|
|
2401
2528
|
timer = null;
|
|
@@ -2413,6 +2540,7 @@ var Calendar = class {
|
|
|
2413
2540
|
this.positionMore();
|
|
2414
2541
|
});
|
|
2415
2542
|
this.resizeObs.observe(this.root);
|
|
2543
|
+
this.resizeObs.observe(this.toolbarEl);
|
|
2416
2544
|
}
|
|
2417
2545
|
}
|
|
2418
2546
|
onAdoptCheck = () => {
|
|
@@ -2493,7 +2621,7 @@ var Calendar = class {
|
|
|
2493
2621
|
defaultAllDayDays: this.opts.defaultAllDayDays
|
|
2494
2622
|
});
|
|
2495
2623
|
}
|
|
2496
|
-
if (has("locale") || has("hour12")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12);
|
|
2624
|
+
if (has("locale") || has("hour12") || has("formatTime")) this.fmt = new Formatter(this.opts.locale, this.opts.hour12, this.opts.formatTime);
|
|
2497
2625
|
if (has("text") || has("locale")) this.text = this.resolveText();
|
|
2498
2626
|
if (has("eventOrder")) this.order = parseOrder(this.opts.eventOrder);
|
|
2499
2627
|
if (has("date")) this.anchor = this.toDay(this.opts.date) ?? this.anchor;
|
|
@@ -2539,7 +2667,12 @@ var Calendar = class {
|
|
|
2539
2667
|
gotoDate(date) {
|
|
2540
2668
|
const day = this.toDay(date);
|
|
2541
2669
|
if (day === null) return;
|
|
2670
|
+
const sameMonth = this.opts.view === "month" && startOfMonth(day) === startOfMonth(this.anchor);
|
|
2542
2671
|
this.anchor = day;
|
|
2672
|
+
if (sameMonth) {
|
|
2673
|
+
this.view = { ...this.view, anchor: day };
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2543
2676
|
this.rebuild(true);
|
|
2544
2677
|
}
|
|
2545
2678
|
prev() {
|
|
@@ -2685,7 +2818,8 @@ var Calendar = class {
|
|
|
2685
2818
|
out.push(...classList(this.opts.dayClassNames?.(this.dayArg(day))));
|
|
2686
2819
|
return out;
|
|
2687
2820
|
}
|
|
2688
|
-
isEditable(n, what) {
|
|
2821
|
+
isEditable(n, what, presentation = false) {
|
|
2822
|
+
if (!presentation && this.itemWriteStates.get(n.id) === "pending") return false;
|
|
2689
2823
|
if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
|
|
2690
2824
|
if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
|
|
2691
2825
|
const it = n.item;
|
|
@@ -2725,13 +2859,17 @@ var Calendar = class {
|
|
|
2725
2859
|
if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
|
|
2726
2860
|
if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
|
|
2727
2861
|
const indicator = variant === "indicator";
|
|
2728
|
-
if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
|
|
2729
|
-
if (this.draggingId === n.id)
|
|
2862
|
+
if (!preview && !indicator && (this.isEditable(n, "start", true) || this.isEditable(n, "duration", true))) el.classList.add("is-draggable");
|
|
2863
|
+
if (this.draggingId === n.id) {
|
|
2864
|
+
el.classList.add("is-dragging");
|
|
2865
|
+
if (variant === "timed" && this.dragWidth !== null) el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
2866
|
+
}
|
|
2730
2867
|
if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
|
|
2731
2868
|
el.classList.add(...classList(it.className));
|
|
2732
2869
|
const arg = this.itemArg(el, meta);
|
|
2733
2870
|
el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
|
|
2734
2871
|
el.dataset.id = n.id;
|
|
2872
|
+
this.applyItemWriteState(el, n.id);
|
|
2735
2873
|
if (it.kind) el.dataset.kind = it.kind;
|
|
2736
2874
|
const color = it.color ?? it.borderColor;
|
|
2737
2875
|
if (color) el.style.setProperty("--tnc-c", color);
|
|
@@ -2748,6 +2886,7 @@ var Calendar = class {
|
|
|
2748
2886
|
const custom = variant === "list" ? this.opts.listItemContent?.(arg) : null;
|
|
2749
2887
|
const content = indicator ? null : custom ?? this.opts.itemContent?.(arg);
|
|
2750
2888
|
if (indicator) {
|
|
2889
|
+
if (this.opts.monthDisplay === "chips") el.append(h(this.doc, "span", "tnc-item-title", null, it.title));
|
|
2751
2890
|
} else if (content != null) {
|
|
2752
2891
|
const wrap = h(this.doc, "div", "tnc-item-main");
|
|
2753
2892
|
wrap.append(content);
|
|
@@ -2760,6 +2899,7 @@ var Calendar = class {
|
|
|
2760
2899
|
return el;
|
|
2761
2900
|
}
|
|
2762
2901
|
refreshItemEl(el, meta) {
|
|
2902
|
+
this.applyItemWriteState(el, meta.n.id);
|
|
2763
2903
|
const time = el.querySelector(":scope .tnc-item-time");
|
|
2764
2904
|
if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
|
|
2765
2905
|
el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
|
|
@@ -2942,6 +3082,7 @@ var Calendar = class {
|
|
|
2942
3082
|
this.current.scrollToTime(this.slot.scroll);
|
|
2943
3083
|
this.current.revealDay(this.anchor);
|
|
2944
3084
|
} else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
3085
|
+
else if (this.current instanceof YearView) this.current.revealDay(this.anchor);
|
|
2945
3086
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
2946
3087
|
}
|
|
2947
3088
|
/** Sets the visible days and view info from the anchor. */
|
|
@@ -2967,9 +3108,11 @@ var Calendar = class {
|
|
|
2967
3108
|
}
|
|
2968
3109
|
/** The range, run on to the end of the strip's days so their dots are right. */
|
|
2969
3110
|
dataRangeFor(range) {
|
|
2970
|
-
const
|
|
2971
|
-
|
|
2972
|
-
|
|
3111
|
+
const window = this.strip?.window();
|
|
3112
|
+
const start = window ? Math.min(range.startDay, window.start) : range.startDay;
|
|
3113
|
+
const end = window ? Math.max(range.endDay, window.end) : range.endDay;
|
|
3114
|
+
if (start === range.startDay && end === range.endDay) return range;
|
|
3115
|
+
return { ...range, startDay: start, start: new Date(toInstant(start, 0, this.zone)), endDay: end, end: new Date(toInstant(end, 0, this.zone)) };
|
|
2973
3116
|
}
|
|
2974
3117
|
/** Adds, updates or removes the date strip for the current view. */
|
|
2975
3118
|
syncStrip(navigated) {
|
|
@@ -2988,7 +3131,9 @@ var Calendar = class {
|
|
|
2988
3131
|
this.root.classList.add("tnc--has-strip");
|
|
2989
3132
|
const { startDay, endDay } = this.view.range;
|
|
2990
3133
|
if (!this.strip) {
|
|
2991
|
-
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown())
|
|
3134
|
+
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown(), (day) => {
|
|
3135
|
+
if (this.agendaList()) this.showListDay(day);
|
|
3136
|
+
});
|
|
2992
3137
|
this.strip = strip;
|
|
2993
3138
|
this.root.insertBefore(strip.el, this.viewHost);
|
|
2994
3139
|
const RO = this.win.ResizeObserver;
|
|
@@ -3016,20 +3161,24 @@ var Calendar = class {
|
|
|
3016
3161
|
stripShown() {
|
|
3017
3162
|
if (!this.strip || this.rebuilding || !this.view) return;
|
|
3018
3163
|
const next = this.dataRangeFor(this.view.range);
|
|
3019
|
-
if (next.endDay === this.view.dataRange.endDay) return;
|
|
3164
|
+
if (next.startDay === this.view.dataRange.startDay && next.endDay === this.view.dataRange.endDay) return;
|
|
3020
3165
|
this.view = { ...this.view, dataRange: next };
|
|
3021
3166
|
this.opts.onRangeChange?.(this.view);
|
|
3022
3167
|
}
|
|
3023
3168
|
/** What is under a point, the date strip included. */
|
|
3024
3169
|
hitAt(x, y) {
|
|
3170
|
+
const target = this.doc.elementFromPoint(x, y);
|
|
3171
|
+
if (!target || !this.root.contains(target)) {
|
|
3172
|
+
this.strip?.markTarget(null);
|
|
3173
|
+
return null;
|
|
3174
|
+
}
|
|
3025
3175
|
const strip = this.strip;
|
|
3026
3176
|
if (strip) {
|
|
3027
|
-
const
|
|
3028
|
-
const day = el ? strip.dayAt(el) : null;
|
|
3177
|
+
const day = strip.dayAt(target);
|
|
3029
3178
|
strip.markTarget(day !== null && this.draggingId ? day : null);
|
|
3030
3179
|
if (day !== null) return { kind: "day", day };
|
|
3031
3180
|
}
|
|
3032
|
-
return this.current?.hit(x, y)
|
|
3181
|
+
return this.current?.el.contains(target) ? this.current.hit(x, y) : null;
|
|
3033
3182
|
}
|
|
3034
3183
|
agendaList() {
|
|
3035
3184
|
return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
|
|
@@ -3143,11 +3292,13 @@ var Calendar = class {
|
|
|
3143
3292
|
buttons.style.display = "flex";
|
|
3144
3293
|
select.style.display = "none";
|
|
3145
3294
|
const startWidth = Array.from(start.children).reduce((width, child) => width + child.scrollWidth, 0);
|
|
3146
|
-
const
|
|
3295
|
+
const availableWidth = Math.min(this.root.clientWidth, this.toolbarEl.clientWidth);
|
|
3296
|
+
const compact = mode === "select" || mode === "auto" && startWidth + buttons.scrollWidth + 32 > availableWidth;
|
|
3147
3297
|
buttons.hidden = compact;
|
|
3148
3298
|
select.hidden = !compact;
|
|
3149
3299
|
buttons.style.display = compact ? "none" : "flex";
|
|
3150
3300
|
select.style.display = compact ? "" : "none";
|
|
3301
|
+
select.parentElement?.classList.toggle("tnc-toolbar-end--compact", compact);
|
|
3151
3302
|
}
|
|
3152
3303
|
applyChange(change) {
|
|
3153
3304
|
if (!change.added.length && !change.updated.length && !change.removed.length && !change.days.size && !change.spans) return;
|
|
@@ -3212,7 +3363,7 @@ var Calendar = class {
|
|
|
3212
3363
|
const s = wallTime(n.startMs, this.zone);
|
|
3213
3364
|
const start = this.fmt.time(s.minute, true);
|
|
3214
3365
|
if (variant === "dot" || variant === "bar" || variant === "indicator") return { start: s.day === day ? start : "", end: "" };
|
|
3215
|
-
if (n.startMs === n.endMs) return { start, end: "" };
|
|
3366
|
+
if (n.startMs === n.endMs || n.item.timeDisplay === "start") return { start, end: "" };
|
|
3216
3367
|
return { start, end: this.fmt.time(wallTime(n.endMs, this.zone).minute, true) };
|
|
3217
3368
|
}
|
|
3218
3369
|
fillTime(el, n, variant, day) {
|
|
@@ -3228,7 +3379,8 @@ var Calendar = class {
|
|
|
3228
3379
|
}
|
|
3229
3380
|
const s = wallTime(n.startMs, this.zone);
|
|
3230
3381
|
const e = wallTime(n.endMs, this.zone);
|
|
3231
|
-
|
|
3382
|
+
const start = `${n.item.title}, ${this.fmt.longDate(s.day)} ${this.fmt.time(s.minute)}`;
|
|
3383
|
+
return n.item.timeDisplay === "start" || n.startMs === n.endMs ? start : `${start} \u2013 ${this.fmt.time(e.minute)}`;
|
|
3232
3384
|
}
|
|
3233
3385
|
defaultContent(meta) {
|
|
3234
3386
|
const { n, variant, day } = meta;
|
|
@@ -3256,7 +3408,7 @@ var Calendar = class {
|
|
|
3256
3408
|
addHandles(el, meta) {
|
|
3257
3409
|
const { n, variant } = meta;
|
|
3258
3410
|
const ports = variant === "timeline" && !meta.mirror && n.id !== EXTERNAL_ID && !!this.opts.timeline?.onLink;
|
|
3259
|
-
if (variant === "dot" || !this.isEditable(n, "duration")) {
|
|
3411
|
+
if (variant === "dot" || !this.isEditable(n, "duration", true)) {
|
|
3260
3412
|
if (ports) this.addPorts(el, n);
|
|
3261
3413
|
return;
|
|
3262
3414
|
}
|
|
@@ -3345,7 +3497,10 @@ var Calendar = class {
|
|
|
3345
3497
|
r.addEventListener("dragover", this.onNativeDragOver);
|
|
3346
3498
|
r.addEventListener("dragleave", this.onNativeDragLeave);
|
|
3347
3499
|
r.addEventListener("drop", this.onNativeDrop);
|
|
3500
|
+
r.addEventListener("touchstart", this.onTouchStart, { passive: true });
|
|
3348
3501
|
r.addEventListener("touchmove", this.onTouchMove, { passive: false });
|
|
3502
|
+
r.addEventListener("touchend", this.onTouchEnd);
|
|
3503
|
+
r.addEventListener("touchcancel", this.onTouchEnd);
|
|
3349
3504
|
r.addEventListener("pointerover", this.onAdoptCheck);
|
|
3350
3505
|
r.addEventListener("focusin", this.onAdoptCheck);
|
|
3351
3506
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -3364,7 +3519,10 @@ var Calendar = class {
|
|
|
3364
3519
|
r.removeEventListener("dragover", this.onNativeDragOver);
|
|
3365
3520
|
r.removeEventListener("dragleave", this.onNativeDragLeave);
|
|
3366
3521
|
r.removeEventListener("drop", this.onNativeDrop);
|
|
3522
|
+
r.removeEventListener("touchstart", this.onTouchStart);
|
|
3367
3523
|
r.removeEventListener("touchmove", this.onTouchMove);
|
|
3524
|
+
r.removeEventListener("touchend", this.onTouchEnd);
|
|
3525
|
+
r.removeEventListener("touchcancel", this.onTouchEnd);
|
|
3368
3526
|
r.removeEventListener("pointerover", this.onAdoptCheck);
|
|
3369
3527
|
r.removeEventListener("focusin", this.onAdoptCheck);
|
|
3370
3528
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -3384,6 +3542,10 @@ var Calendar = class {
|
|
|
3384
3542
|
return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
|
|
3385
3543
|
}
|
|
3386
3544
|
onDocPointerDown = (ev) => {
|
|
3545
|
+
if (this.cancelledPointerId !== null) {
|
|
3546
|
+
this.cancelledPointerId = null;
|
|
3547
|
+
this.listenDoc(false);
|
|
3548
|
+
}
|
|
3387
3549
|
const t = ev.target;
|
|
3388
3550
|
if (this.popover && !this.popover.el.contains(t) && !this.popover.anchor.contains(t)) this.closeMore();
|
|
3389
3551
|
if (this.selection && !this.root.contains(t)) this.unselect();
|
|
@@ -3391,6 +3553,7 @@ var Calendar = class {
|
|
|
3391
3553
|
onPointerDown = (ev) => {
|
|
3392
3554
|
if (ev.button !== 0 || this.session) return;
|
|
3393
3555
|
const target = ev.target;
|
|
3556
|
+
if (target.closest(".tnc-dg--compact .tnc-dg-day")) return;
|
|
3394
3557
|
if (target.closest("button, a, input, select, textarea, .tnc-toolbar, .tnc-port, [data-tnc-no-drag]")) {
|
|
3395
3558
|
if (!target.closest(".tnc-item")) return;
|
|
3396
3559
|
if (target.closest(".tnc-port, [data-tnc-no-drag], input, select, textarea")) return;
|
|
@@ -3423,7 +3586,7 @@ var Calendar = class {
|
|
|
3423
3586
|
y0: ev.clientY,
|
|
3424
3587
|
active: false,
|
|
3425
3588
|
origin,
|
|
3426
|
-
grab: overflowOrigin
|
|
3589
|
+
grab: overflowOrigin ? this.current instanceof TimeGridView && !origin?.allDay ? { kind: "time", day: overflowOrigin.day, minute: overflowOrigin.minute } : { kind: "day", day: this.meta.get(itemEl).day } : this.current?.hit(ev.clientX, ev.clientY) ?? null,
|
|
3427
3590
|
proposal: null,
|
|
3428
3591
|
denied: false,
|
|
3429
3592
|
timer: null,
|
|
@@ -3448,20 +3611,159 @@ var Calendar = class {
|
|
|
3448
3611
|
};
|
|
3449
3612
|
activate(s) {
|
|
3450
3613
|
s.active = true;
|
|
3614
|
+
if (s.lastEvent) this.leaveHover(s.lastEvent);
|
|
3451
3615
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
3452
3616
|
s.timer = null;
|
|
3453
3617
|
if (s.origin) {
|
|
3454
3618
|
this.draggingId = s.origin.id;
|
|
3455
|
-
for (const el of this.elementsFor(s.origin.id))
|
|
3619
|
+
for (const el of this.elementsFor(s.origin.id)) {
|
|
3620
|
+
if (this.meta.get(el)?.variant === "timed" && s.mode === "move") {
|
|
3621
|
+
this.dragWidth = el.getBoundingClientRect().width;
|
|
3622
|
+
el.style.setProperty("--tnc-drag-width", `${this.dragWidth}px`);
|
|
3623
|
+
}
|
|
3624
|
+
if (this.opts.view !== "list" && !(this.current instanceof DayGridView) && s.mode === "move") {
|
|
3625
|
+
const source = el.cloneNode(true);
|
|
3626
|
+
source.classList.remove("is-selected");
|
|
3627
|
+
source.classList.add("tnc-drag-source");
|
|
3628
|
+
source.setAttribute("aria-hidden", "true");
|
|
3629
|
+
source.inert = true;
|
|
3630
|
+
for (const node of [source, ...source.querySelectorAll("[id], [data-id], [data-task-path]")]) {
|
|
3631
|
+
node.removeAttribute("id");
|
|
3632
|
+
node.removeAttribute("data-id");
|
|
3633
|
+
node.removeAttribute("data-task-path");
|
|
3634
|
+
}
|
|
3635
|
+
el.before(source);
|
|
3636
|
+
this.dragSources.push(source);
|
|
3637
|
+
}
|
|
3638
|
+
if (this.opts.view === "list" && !this.listDragPreview && s.mode === "move") {
|
|
3639
|
+
const bounds = el.getBoundingClientRect();
|
|
3640
|
+
const preview = el.cloneNode(true);
|
|
3641
|
+
preview.classList.add("tnc-drag-preview", "is-dragging");
|
|
3642
|
+
preview.removeAttribute("data-id");
|
|
3643
|
+
preview.removeAttribute("data-task-path");
|
|
3644
|
+
preview.setAttribute("aria-hidden", "true");
|
|
3645
|
+
preview.inert = true;
|
|
3646
|
+
preview.style.width = `${bounds.width}px`;
|
|
3647
|
+
this.root.append(preview);
|
|
3648
|
+
this.listDragPreview = { element: preview, offsetX: s.x0 - bounds.left, offsetY: s.y0 - bounds.top, width: bounds.width, height: bounds.height };
|
|
3649
|
+
}
|
|
3650
|
+
el.classList.add("is-dragging");
|
|
3651
|
+
}
|
|
3456
3652
|
}
|
|
3457
3653
|
this.closeMore();
|
|
3458
3654
|
if (s.mode !== "select") this.unselectSilently();
|
|
3459
3655
|
this.root.classList.add("tnc-dragging", `tnc-dragging-${s.mode}`);
|
|
3460
3656
|
if (s.lastEvent) this.track(s, s.lastEvent);
|
|
3657
|
+
this.startDragScroll();
|
|
3658
|
+
}
|
|
3659
|
+
startDragScroll() {
|
|
3660
|
+
if (this.dragScrollFrame !== null) return;
|
|
3661
|
+
const tick = () => {
|
|
3662
|
+
this.dragScrollFrame = null;
|
|
3663
|
+
const session = this.session;
|
|
3664
|
+
const pointer = session?.lastEvent;
|
|
3665
|
+
if (!session?.active || !pointer) return;
|
|
3666
|
+
const origin = session.origin ? this.elementsFor(session.origin.id)[0] : this.viewHost;
|
|
3667
|
+
const underPointer = this.doc.elementFromPoint(pointer.clientX, pointer.clientY);
|
|
3668
|
+
let moved = false;
|
|
3669
|
+
if (origin && underPointer && this.root.contains(underPointer)) {
|
|
3670
|
+
for (const axis of ["x", "y"]) {
|
|
3671
|
+
const scroller = scrollerOf(origin, axis);
|
|
3672
|
+
if (!scroller) continue;
|
|
3673
|
+
const viewport = scroller === this.doc.scrollingElement ? { left: 0, top: 0, right: this.win.innerWidth, bottom: this.win.innerHeight } : scroller.getBoundingClientRect();
|
|
3674
|
+
const coordinate = axis === "x" ? pointer.clientX : pointer.clientY;
|
|
3675
|
+
const lower = Math.max(0, axis === "x" ? viewport.left : viewport.top);
|
|
3676
|
+
const upper = Math.min(axis === "x" ? this.win.innerWidth : this.win.innerHeight, axis === "x" ? viewport.right : viewport.bottom);
|
|
3677
|
+
if (coordinate < lower || coordinate > upper) {
|
|
3678
|
+
this.dragScrollEdges.delete(axis);
|
|
3679
|
+
continue;
|
|
3680
|
+
}
|
|
3681
|
+
const delta = coordinate < lower + 32 ? -Math.min(12, (lower + 32 - coordinate) / 2) : coordinate > upper - 32 ? Math.min(12, (coordinate - upper + 32) / 2) : 0;
|
|
3682
|
+
if (!delta) {
|
|
3683
|
+
this.dragScrollEdges.delete(axis);
|
|
3684
|
+
continue;
|
|
3685
|
+
}
|
|
3686
|
+
const edge = this.dragScrollEdges.get(axis);
|
|
3687
|
+
const direction = Math.sign(delta);
|
|
3688
|
+
const now = this.win.performance.now();
|
|
3689
|
+
if (!edge || edge.scroller !== scroller || edge.direction !== direction) {
|
|
3690
|
+
this.dragScrollEdges.set(axis, { scroller, direction, since: now });
|
|
3691
|
+
continue;
|
|
3692
|
+
}
|
|
3693
|
+
if (now - edge.since < 180) continue;
|
|
3694
|
+
if (!this.dragScrollOrigins.has(scroller)) this.dragScrollOrigins.set(scroller, { top: scroller.scrollTop, left: scroller.scrollLeft });
|
|
3695
|
+
const before = axis === "x" ? scroller.scrollLeft : scroller.scrollTop;
|
|
3696
|
+
if (axis === "x") scroller.scrollLeft += delta;
|
|
3697
|
+
else scroller.scrollTop += delta;
|
|
3698
|
+
moved ||= before !== (axis === "x" ? scroller.scrollLeft : scroller.scrollTop);
|
|
3699
|
+
}
|
|
3700
|
+
} else this.dragScrollEdges.clear();
|
|
3701
|
+
if (moved) this.track(session, pointer);
|
|
3702
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
3703
|
+
};
|
|
3704
|
+
this.dragScrollFrame = this.win.requestAnimationFrame(tick);
|
|
3461
3705
|
}
|
|
3706
|
+
onTouchStart = (ev) => {
|
|
3707
|
+
const first = ev.touches[0];
|
|
3708
|
+
const second = ev.touches[1];
|
|
3709
|
+
if (!first || !second) {
|
|
3710
|
+
this.endPinch();
|
|
3711
|
+
return;
|
|
3712
|
+
}
|
|
3713
|
+
const view = this.current;
|
|
3714
|
+
if (this.session?.active || !this.opts.pinchZoom || !(view instanceof TimeGridView)) return;
|
|
3715
|
+
const body = first.target.closest(".tnc-tg-body");
|
|
3716
|
+
if (!body?.contains(second.target)) return;
|
|
3717
|
+
this.endSession(null, true);
|
|
3718
|
+
if (this.pinch) return;
|
|
3719
|
+
const { top, height } = body.getBoundingClientRect();
|
|
3720
|
+
const y = (first.clientY + second.clientY) / 2;
|
|
3721
|
+
this.pinch = {
|
|
3722
|
+
body,
|
|
3723
|
+
distance: Math.max(1, Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY)),
|
|
3724
|
+
y,
|
|
3725
|
+
anchor: (y - top) / height,
|
|
3726
|
+
drawn: view.drawnSlotHeight,
|
|
3727
|
+
option: this.opts.slotHeight
|
|
3728
|
+
};
|
|
3729
|
+
};
|
|
3462
3730
|
onTouchMove = (ev) => {
|
|
3463
3731
|
if (this.session?.active) ev.preventDefault();
|
|
3732
|
+
const p = this.pinch;
|
|
3733
|
+
const first = ev.touches[0];
|
|
3734
|
+
const second = ev.touches[1];
|
|
3735
|
+
if (!p || !first || !second) return;
|
|
3736
|
+
if (!ev.cancelable) {
|
|
3737
|
+
this.endPinch();
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
ev.preventDefault();
|
|
3741
|
+
const height = p.drawn * Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY) / p.distance;
|
|
3742
|
+
p.y = (first.clientY + second.clientY) / 2;
|
|
3743
|
+
this.pinchTo(p, Math.min(Math.max(p.drawn, PINCH_MAX_SLOT), Math.max(Math.min(p.drawn, PINCH_MIN_SLOT), height)));
|
|
3744
|
+
};
|
|
3745
|
+
onTouchEnd = (ev) => {
|
|
3746
|
+
if (ev.touches.length < 2) this.endPinch();
|
|
3464
3747
|
};
|
|
3748
|
+
/** Rescales the grid, keeping the anchored time under the fingers. */
|
|
3749
|
+
pinchTo(p, slotHeight) {
|
|
3750
|
+
this.opts = { ...this.opts, slotHeight };
|
|
3751
|
+
if (!(this.current instanceof TimeGridView)) return;
|
|
3752
|
+
this.current.applyScale();
|
|
3753
|
+
const { top, height } = p.body.getBoundingClientRect();
|
|
3754
|
+
const scroller = scrollerOf(p.body, "y");
|
|
3755
|
+
if (scroller) scroller.scrollTop += top + height * p.anchor - p.y;
|
|
3756
|
+
}
|
|
3757
|
+
endPinch() {
|
|
3758
|
+
const p = this.pinch;
|
|
3759
|
+
this.pinch = null;
|
|
3760
|
+
if (!p?.body.isConnected) return;
|
|
3761
|
+
const settled = Math.round(this.opts.slotHeight);
|
|
3762
|
+
const slotHeight = settled === Math.round(p.drawn) ? p.option : settled;
|
|
3763
|
+
this.pinchTo(p, slotHeight);
|
|
3764
|
+
this.current?.render(null);
|
|
3765
|
+
if (slotHeight !== p.option) this.opts.onSlotHeightChange?.(slotHeight);
|
|
3766
|
+
}
|
|
3465
3767
|
onPointerMove = (ev) => {
|
|
3466
3768
|
const s = this.session;
|
|
3467
3769
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
@@ -3487,6 +3789,12 @@ var Calendar = class {
|
|
|
3487
3789
|
this.track(s, ev);
|
|
3488
3790
|
};
|
|
3489
3791
|
onPointerUp = (ev) => {
|
|
3792
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
3793
|
+
this.cancelledPointerId = null;
|
|
3794
|
+
this.suppressNextClick();
|
|
3795
|
+
this.listenDoc(false);
|
|
3796
|
+
return;
|
|
3797
|
+
}
|
|
3490
3798
|
const s = this.session;
|
|
3491
3799
|
if (!s || ev.pointerId !== s.pointerId) return;
|
|
3492
3800
|
if (!s.active) {
|
|
@@ -3499,28 +3807,49 @@ var Calendar = class {
|
|
|
3499
3807
|
return;
|
|
3500
3808
|
}
|
|
3501
3809
|
this.suppressNextClick();
|
|
3810
|
+
this.track(s, ev);
|
|
3502
3811
|
this.endSession(ev, false);
|
|
3503
3812
|
};
|
|
3504
3813
|
onPointerCancel = (ev) => {
|
|
3814
|
+
if (ev.pointerId === this.cancelledPointerId) {
|
|
3815
|
+
this.cancelledPointerId = null;
|
|
3816
|
+
this.listenDoc(false);
|
|
3817
|
+
}
|
|
3505
3818
|
if (this.session && ev.pointerId === this.session.pointerId) this.endSession(null, true);
|
|
3506
3819
|
};
|
|
3507
3820
|
onDocKeyDown = (ev) => {
|
|
3508
3821
|
if (ev.key === "Escape" && this.session) {
|
|
3509
3822
|
ev.preventDefault();
|
|
3510
3823
|
ev.stopPropagation();
|
|
3824
|
+
const pointerId = this.session.active ? this.session.pointerId : null;
|
|
3511
3825
|
this.endSession(null, true);
|
|
3826
|
+
if (pointerId !== null) {
|
|
3827
|
+
this.cancelledPointerId = pointerId;
|
|
3828
|
+
this.listenDoc(true);
|
|
3829
|
+
}
|
|
3512
3830
|
}
|
|
3513
3831
|
};
|
|
3514
3832
|
/** Proposal for the current pointer position. */
|
|
3515
3833
|
track(s, ev) {
|
|
3834
|
+
if (this.listDragPreview) {
|
|
3835
|
+
const preview = this.listDragPreview;
|
|
3836
|
+
const bounds = this.root.getBoundingClientRect();
|
|
3837
|
+
preview.element.style.left = `${Math.max(8, Math.min(this.win.innerWidth - preview.width - 8, ev.clientX - preview.offsetX)) - bounds.left}px`;
|
|
3838
|
+
preview.element.style.top = `${Math.max(8, Math.min(this.win.innerHeight - preview.height - 8, ev.clientY - preview.offsetY)) - bounds.top}px`;
|
|
3839
|
+
}
|
|
3516
3840
|
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
3517
3841
|
if (s.mode === "external") {
|
|
3518
3842
|
this.trackExternal(s, hit);
|
|
3519
3843
|
return;
|
|
3520
3844
|
}
|
|
3521
3845
|
if (!s.grab) s.grab = hit;
|
|
3522
|
-
if (!hit || !s.grab)
|
|
3846
|
+
if (!hit || !s.grab) {
|
|
3847
|
+
this.invalidateProposal(s);
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3523
3850
|
if (s.mode === "select") {
|
|
3851
|
+
s.denied = false;
|
|
3852
|
+
this.root.classList.remove("tnc-drop-denied");
|
|
3524
3853
|
const sel = this.selectionFor(s.grab, hit);
|
|
3525
3854
|
if (!sel) return;
|
|
3526
3855
|
this.selection = sel;
|
|
@@ -3529,25 +3858,40 @@ var Calendar = class {
|
|
|
3529
3858
|
}
|
|
3530
3859
|
const origin = s.origin;
|
|
3531
3860
|
const p = s.mode === "move" ? this.moveProposal(origin, s.grab, hit) : this.resizeProposal(origin, hit, s.mode === "resize-start");
|
|
3532
|
-
if (!p)
|
|
3861
|
+
if (!p) {
|
|
3862
|
+
this.invalidateProposal(s);
|
|
3863
|
+
return;
|
|
3864
|
+
}
|
|
3533
3865
|
const allowed = this.opts.canDrop?.({ item: origin.item, start: new Date(p.startMs), end: new Date(p.endMs), allDay: p.allDay, rowId: p.rowId }) ?? true;
|
|
3534
3866
|
s.denied = !allowed;
|
|
3535
3867
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
3536
|
-
if (!allowed
|
|
3868
|
+
if (!allowed) {
|
|
3869
|
+
this.invalidateProposal(s);
|
|
3870
|
+
return;
|
|
3871
|
+
}
|
|
3872
|
+
if (sameProposal(p, s.proposal)) return;
|
|
3537
3873
|
s.proposal = p;
|
|
3538
3874
|
this.dragRow = p.rowId !== void 0 ? { id: origin.id, rowId: p.rowId } : null;
|
|
3539
|
-
this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
3875
|
+
if (this.opts.view !== "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
3876
|
+
}
|
|
3877
|
+
invalidateProposal(s) {
|
|
3878
|
+
const hadProposal = s.proposal !== null;
|
|
3879
|
+
s.proposal = null;
|
|
3880
|
+
s.hit = null;
|
|
3881
|
+
s.denied = true;
|
|
3882
|
+
this.dragRow = null;
|
|
3883
|
+
this.root.classList.add("tnc-drop-denied");
|
|
3884
|
+
this.strip?.markTarget(null);
|
|
3885
|
+
this.unselectSilently();
|
|
3886
|
+
if (s.mode === "external") this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
3887
|
+
else if (hadProposal && s.origin) this.applyChange(this.store.upsert([s.origin.item]));
|
|
3540
3888
|
}
|
|
3541
3889
|
trackExternal(s, raw) {
|
|
3542
3890
|
const src = s.source;
|
|
3543
3891
|
const hit = raw?.kind === "timeline" && raw.area !== "track" && raw.area !== "header" ? null : raw;
|
|
3544
3892
|
s.hit = hit;
|
|
3545
3893
|
if (!hit) {
|
|
3546
|
-
|
|
3547
|
-
s.proposal = null;
|
|
3548
|
-
this.applyChange(this.store.remove([EXTERNAL_ID]));
|
|
3549
|
-
}
|
|
3550
|
-
this.unselectSilently();
|
|
3894
|
+
this.invalidateProposal(s);
|
|
3551
3895
|
return;
|
|
3552
3896
|
}
|
|
3553
3897
|
const dur = src.durationMinutes ?? this.opts.defaultTimedMinutes;
|
|
@@ -3566,6 +3910,10 @@ var Calendar = class {
|
|
|
3566
3910
|
const allowed = this.opts.canDrop?.({ item: this.externalItem(src, p), start: new Date(p.startMs), end: new Date(p.endMs), allDay: p.allDay, rowId: p.rowId }) ?? true;
|
|
3567
3911
|
s.denied = !allowed;
|
|
3568
3912
|
this.root.classList.toggle("tnc-drop-denied", !allowed);
|
|
3913
|
+
if (!allowed) {
|
|
3914
|
+
this.invalidateProposal(s);
|
|
3915
|
+
return;
|
|
3916
|
+
}
|
|
3569
3917
|
if (sameProposal(p, s.proposal)) return;
|
|
3570
3918
|
s.proposal = p;
|
|
3571
3919
|
if (hit.kind === "timeline" && p.rowId === void 0) {
|
|
@@ -3758,7 +4106,19 @@ var Calendar = class {
|
|
|
3758
4106
|
endSession(ev, cancel) {
|
|
3759
4107
|
const s = this.session;
|
|
3760
4108
|
if (!s) return;
|
|
4109
|
+
if (this.dragScrollFrame !== null) this.win.cancelAnimationFrame(this.dragScrollFrame);
|
|
4110
|
+
this.dragScrollFrame = null;
|
|
4111
|
+
if (cancel || s.denied) for (const [scroller, position] of this.dragScrollOrigins) {
|
|
4112
|
+
scroller.scrollTop = position.top;
|
|
4113
|
+
scroller.scrollLeft = position.left;
|
|
4114
|
+
}
|
|
4115
|
+
this.dragScrollOrigins.clear();
|
|
4116
|
+
this.dragScrollEdges.clear();
|
|
3761
4117
|
this.session = null;
|
|
4118
|
+
this.listDragPreview?.element.remove();
|
|
4119
|
+
this.listDragPreview = null;
|
|
4120
|
+
for (const source of this.dragSources) source.remove();
|
|
4121
|
+
this.dragSources = [];
|
|
3762
4122
|
if (s.timer !== null) this.win.clearTimeout(s.timer);
|
|
3763
4123
|
this.listenDoc(false);
|
|
3764
4124
|
this.root.classList.remove(
|
|
@@ -3772,10 +4132,15 @@ var Calendar = class {
|
|
|
3772
4132
|
);
|
|
3773
4133
|
const draggedId = this.draggingId;
|
|
3774
4134
|
this.draggingId = null;
|
|
4135
|
+
this.dragWidth = null;
|
|
3775
4136
|
this.dragRow = null;
|
|
3776
|
-
if (draggedId) for (const el of this.elementsFor(draggedId))
|
|
4137
|
+
if (draggedId) for (const el of this.elementsFor(draggedId)) {
|
|
4138
|
+
el.classList.remove("is-dragging");
|
|
4139
|
+
el.style.removeProperty("--tnc-drag-width");
|
|
4140
|
+
}
|
|
3777
4141
|
this.strip?.markTarget(null);
|
|
3778
4142
|
if (!s.active) return;
|
|
4143
|
+
if (this.current instanceof DayGridView) this.current.render(null);
|
|
3779
4144
|
if (s.mode === "select") {
|
|
3780
4145
|
if (cancel) this.unselectSilently();
|
|
3781
4146
|
else if (this.selection) this.fireSelect(this.selection, ev);
|
|
@@ -3827,10 +4192,16 @@ var Calendar = class {
|
|
|
3827
4192
|
revert();
|
|
3828
4193
|
return;
|
|
3829
4194
|
}
|
|
4195
|
+
if (this.opts.view === "list") this.applyChange(this.store.upsert([this.withProposal(origin, p)]));
|
|
3830
4196
|
this.flush();
|
|
3831
4197
|
this.commit(s.mode === "move" ? "drop" : "resize", origin, p, ev, revert);
|
|
3832
4198
|
}
|
|
3833
4199
|
commit(kind, origin, p, ev, revert) {
|
|
4200
|
+
let reverted = false;
|
|
4201
|
+
const mark = (state) => {
|
|
4202
|
+
this.itemWriteStates.set(origin.id, state);
|
|
4203
|
+
for (const element of this.elementsFor(origin.id)) this.applyItemWriteState(element, origin.id);
|
|
4204
|
+
};
|
|
3834
4205
|
const arg = {
|
|
3835
4206
|
item: origin.item,
|
|
3836
4207
|
start: new Date(p.startMs),
|
|
@@ -3846,19 +4217,32 @@ var Calendar = class {
|
|
|
3846
4217
|
jsEvent: ev,
|
|
3847
4218
|
source: ev && "key" in ev ? "keyboard" : "pointer",
|
|
3848
4219
|
view: this.view,
|
|
3849
|
-
revert
|
|
4220
|
+
revert: () => {
|
|
4221
|
+
reverted = true;
|
|
4222
|
+
revert();
|
|
4223
|
+
mark("failed");
|
|
4224
|
+
}
|
|
3850
4225
|
};
|
|
3851
4226
|
const handler = kind === "drop" ? this.opts.onItemDrop : this.opts.onItemResize;
|
|
3852
4227
|
try {
|
|
4228
|
+
mark("pending");
|
|
3853
4229
|
const res = handler?.(arg);
|
|
3854
4230
|
if (res && typeof res.then === "function") {
|
|
3855
|
-
Promise.resolve(res).
|
|
3856
|
-
|
|
4231
|
+
void Promise.resolve(res).then(() => {
|
|
4232
|
+
if (!reverted) mark("changed");
|
|
4233
|
+
}, () => arg.revert());
|
|
4234
|
+
} else if (!reverted) mark("changed");
|
|
3857
4235
|
} catch (err) {
|
|
3858
|
-
revert();
|
|
4236
|
+
arg.revert();
|
|
3859
4237
|
throw err;
|
|
3860
4238
|
}
|
|
3861
4239
|
}
|
|
4240
|
+
applyItemWriteState(element, id) {
|
|
4241
|
+
const state = this.itemWriteStates.get(id);
|
|
4242
|
+
for (const name of ["pending", "failed", "changed"]) element.classList.toggle(`is-write-${name}`, state === name);
|
|
4243
|
+
if (state === "pending") element.setAttribute("aria-busy", "true");
|
|
4244
|
+
else element.removeAttribute("aria-busy");
|
|
4245
|
+
}
|
|
3862
4246
|
/* --------------------------------------------------------- click / keys */
|
|
3863
4247
|
/** Runs in the capture phase so the click ending a drag never reaches content inside the calendar, React portals included. */
|
|
3864
4248
|
onClickCapture = (ev) => {
|
|
@@ -3868,6 +4252,12 @@ var Calendar = class {
|
|
|
3868
4252
|
ev.preventDefault();
|
|
3869
4253
|
};
|
|
3870
4254
|
onClick = (ev) => {
|
|
4255
|
+
const compactDay = ev.target.closest?.(".tnc-dg--compact .tnc-dg-day");
|
|
4256
|
+
if (compactDay && this.root.contains(compactDay) && !ev.target.closest(".tnc-navlink")) {
|
|
4257
|
+
const day = parseDay(compactDay.dataset.date ?? "");
|
|
4258
|
+
if (day !== null) this.openMore(day, compactDay, ev);
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
3871
4261
|
const yearDay = ev.target.closest?.(".tnc-ym-day");
|
|
3872
4262
|
if (yearDay && this.root.contains(yearDay)) {
|
|
3873
4263
|
const day = parseDay(yearDay.dataset.date ?? "");
|
|
@@ -3956,7 +4346,8 @@ var Calendar = class {
|
|
|
3956
4346
|
const day = parseDay(target.dataset.date ?? "");
|
|
3957
4347
|
if (day !== null) {
|
|
3958
4348
|
ev.preventDefault();
|
|
3959
|
-
|
|
4349
|
+
if (target.closest(".tnc-dg--compact")) this.openMore(day, target, ev);
|
|
4350
|
+
else this.clickSlot({ kind: "day", day }, ev, true);
|
|
3960
4351
|
}
|
|
3961
4352
|
return;
|
|
3962
4353
|
}
|
|
@@ -4044,9 +4435,13 @@ var Calendar = class {
|
|
|
4044
4435
|
const dt = ev.dataTransfer;
|
|
4045
4436
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
4046
4437
|
ev.preventDefault();
|
|
4438
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4439
|
+
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") {
|
|
4440
|
+
dt.dropEffect = "none";
|
|
4441
|
+
this.unselectSilently();
|
|
4442
|
+
return;
|
|
4443
|
+
}
|
|
4047
4444
|
dt.dropEffect = "copy";
|
|
4048
|
-
const hit = this.current.hit(ev.clientX, ev.clientY);
|
|
4049
|
-
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
4050
4445
|
const sel = this.selectionFor(hit, hit);
|
|
4051
4446
|
if (!sel) return;
|
|
4052
4447
|
this.selection = sel;
|
|
@@ -4060,7 +4455,7 @@ var Calendar = class {
|
|
|
4060
4455
|
const dt = ev.dataTransfer;
|
|
4061
4456
|
if (!dt || !this.opts.acceptNativeDrop?.(dt) || !this.current) return;
|
|
4062
4457
|
ev.preventDefault();
|
|
4063
|
-
const hit = this.
|
|
4458
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
4064
4459
|
this.unselectSilently();
|
|
4065
4460
|
if (!hit || hit.kind === "timeline" && hit.area !== "track" && hit.area !== "header") return;
|
|
4066
4461
|
const sel = this.selectionFor(hit, hit);
|
|
@@ -4091,7 +4486,7 @@ function scrollerOf(el, axis) {
|
|
|
4091
4486
|
return doc.scrollingElement;
|
|
4092
4487
|
}
|
|
4093
4488
|
function rangeMoved(prev, next) {
|
|
4094
|
-
return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.endDay !== next.dataRange.endDay;
|
|
4489
|
+
return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.startDay !== next.dataRange.startDay || prev.dataRange.endDay !== next.dataRange.endDay;
|
|
4095
4490
|
}
|
|
4096
4491
|
|
|
4097
4492
|
export {
|