@tasknotes/calendar 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +7 -2
- package/dist/calendar.css +78 -6
- package/dist/cjs/index.cjs +390 -232
- package/dist/cjs/react.cjs +390 -232
- package/dist/esm/{chunk-XHPZ7K4W.js → chunk-25TY2UVR.js} +390 -232
- package/dist/esm/index.js +1 -1
- package/dist/esm/react.js +1 -1
- package/dist/types/core/types.d.cts +2 -0
- package/dist/types/core/types.d.ts +2 -0
- package/dist/types/dom/calendar.d.cts +14 -1
- package/dist/types/dom/calendar.d.ts +14 -1
- package/dist/types/dom/context.d.cts +4 -3
- package/dist/types/dom/context.d.ts +4 -3
- package/dist/types/dom/options.d.cts +33 -2
- package/dist/types/dom/options.d.ts +33 -2
- package/dist/types/dom/views/list.d.cts +10 -8
- package/dist/types/dom/views/list.d.ts +10 -8
- package/dist/types/dom/views/strip.d.cts +26 -6
- package/dist/types/dom/views/strip.d.ts +26 -6
- package/package.json +1 -1
|
@@ -165,6 +165,9 @@ function defaultOptions() {
|
|
|
165
165
|
listDayCount: 7,
|
|
166
166
|
listStart: "date",
|
|
167
167
|
listAgenda: false,
|
|
168
|
+
dateStrip: false,
|
|
169
|
+
listLeadTitle: null,
|
|
170
|
+
listAddText: null,
|
|
168
171
|
monthDisplay: "full",
|
|
169
172
|
timeZone: "local",
|
|
170
173
|
locale: void 0,
|
|
@@ -215,6 +218,9 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
215
218
|
"listDayCount",
|
|
216
219
|
"listStart",
|
|
217
220
|
"listAgenda",
|
|
221
|
+
"dateStrip",
|
|
222
|
+
"listLeadTitle",
|
|
223
|
+
"listAddText",
|
|
218
224
|
"monthDisplay",
|
|
219
225
|
"timeZone",
|
|
220
226
|
"locale",
|
|
@@ -506,17 +512,22 @@ var DayGridView = class {
|
|
|
506
512
|
const n = w.days.length;
|
|
507
513
|
const hidden = new Array(n).fill([]).map(() => []);
|
|
508
514
|
w.pool.begin();
|
|
515
|
+
const previewLevels = new Array(n).fill(0);
|
|
509
516
|
let maxLevel = -1;
|
|
510
517
|
for (const p of row.placements) {
|
|
511
518
|
let level = p.level;
|
|
512
519
|
let room = Infinity;
|
|
513
520
|
for (let c = p.startCol; c < p.endCol; c++) room = Math.min(room, visible(c));
|
|
514
521
|
if (level >= room) {
|
|
515
|
-
if (p.item.id !== ctx.draggingId) {
|
|
522
|
+
if (!p.item.item.preview && p.item.id !== ctx.draggingId) {
|
|
516
523
|
for (let c = p.startCol; c < p.endCol; c++) hidden[c].push(p.item);
|
|
517
524
|
continue;
|
|
518
525
|
}
|
|
519
|
-
|
|
526
|
+
if (p.item.item.preview) {
|
|
527
|
+
level = 0;
|
|
528
|
+
for (let c = p.startCol; c < p.endCol; c++) level = Math.max(level, visible(c) + 1, previewLevels[c]);
|
|
529
|
+
for (let c = p.startCol; c < p.endCol; c++) previewLevels[c] = level + 1;
|
|
530
|
+
} else level = Math.max(0, room - 1);
|
|
520
531
|
}
|
|
521
532
|
maxLevel = Math.max(maxLevel, level);
|
|
522
533
|
const single = !p.item.allDay && p.item.endDay - p.item.startDay <= 1;
|
|
@@ -642,12 +653,244 @@ var DayGridView = class {
|
|
|
642
653
|
}
|
|
643
654
|
};
|
|
644
655
|
|
|
656
|
+
// src/dom/views/list.ts
|
|
657
|
+
var LOAD_AHEAD = "0px 0px 600px 0px";
|
|
658
|
+
var ListView = class {
|
|
659
|
+
constructor(ctx) {
|
|
660
|
+
this.ctx = ctx;
|
|
661
|
+
const { doc, win, opts } = ctx;
|
|
662
|
+
this.el = h(doc, "div", "tnc-list");
|
|
663
|
+
this.scroller = h(doc, "div", "tnc-list-scroll");
|
|
664
|
+
this.body = h(doc, "div", "tnc-list-body", { role: "list", "aria-label": ctx.view.title });
|
|
665
|
+
this.scroller.append(this.body);
|
|
666
|
+
this.el.append(this.scroller);
|
|
667
|
+
this.agenda = opts.listAgenda;
|
|
668
|
+
if (!this.agenda) return;
|
|
669
|
+
this.el.classList.add("tnc-list--agenda");
|
|
670
|
+
this.sentinel = h(doc, "div", "tnc-list-more", { "aria-hidden": "true" });
|
|
671
|
+
this.scroller.append(this.sentinel);
|
|
672
|
+
const IO = win.IntersectionObserver;
|
|
673
|
+
if (IO) {
|
|
674
|
+
this.loader = new IO(
|
|
675
|
+
(entries) => {
|
|
676
|
+
if (entries.some((e) => e.isIntersecting)) ctx.extendList(ctx.view.range.endDay + Math.max(7, opts.listDayCount));
|
|
677
|
+
},
|
|
678
|
+
// Auto height scrolls the page, so watch the viewport; fixed height scrolls the list.
|
|
679
|
+
{ root: opts.height === "auto" ? null : this.scroller, rootMargin: LOAD_AHEAD }
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
win.addEventListener("scroll", this.onScroll, { capture: true, passive: true });
|
|
683
|
+
this.scroller.addEventListener("wheel", this.fold, { passive: true });
|
|
684
|
+
this.scroller.addEventListener("touchmove", this.fold, { passive: true });
|
|
685
|
+
}
|
|
686
|
+
ctx;
|
|
687
|
+
el;
|
|
688
|
+
scroller;
|
|
689
|
+
body;
|
|
690
|
+
agenda;
|
|
691
|
+
pools = /* @__PURE__ */ new Map();
|
|
692
|
+
sections = /* @__PURE__ */ new Map();
|
|
693
|
+
lead = null;
|
|
694
|
+
sentinel = null;
|
|
695
|
+
loader = null;
|
|
696
|
+
frame = null;
|
|
697
|
+
/** A picked day stays marked until the list is scrolled by hand. */
|
|
698
|
+
pinned = false;
|
|
699
|
+
itemsOn(day) {
|
|
700
|
+
const { store, opts } = this.ctx;
|
|
701
|
+
const out = store.spanningIn(day, day + 1, true);
|
|
702
|
+
const seen = new Set(out.map((n) => n.id));
|
|
703
|
+
for (const n of store.timedOn(day)) if (!seen.has(n.id)) out.push(n);
|
|
704
|
+
return out.filter((n) => n.item.display !== "background" && n.item.display !== "none" && (opts.allDaySlot || !n.allDay)).sort((a, b) => Number(b.allDay) - Number(a.allDay) || this.ctx.order(a, b));
|
|
705
|
+
}
|
|
706
|
+
render(_change) {
|
|
707
|
+
const { ctx, agenda } = this;
|
|
708
|
+
const { doc, days, opts } = ctx;
|
|
709
|
+
const includes = agenda && opts.listLeadTitle !== null ? opts.listLeadIncludes : void 0;
|
|
710
|
+
const leadItems = [];
|
|
711
|
+
const inLead = /* @__PURE__ */ new Set();
|
|
712
|
+
const keep = /* @__PURE__ */ new Set();
|
|
713
|
+
let prev = null;
|
|
714
|
+
for (const day of days) {
|
|
715
|
+
let items = this.itemsOn(day);
|
|
716
|
+
if (includes) {
|
|
717
|
+
items = items.filter((n) => {
|
|
718
|
+
if (!includes(n.item)) return true;
|
|
719
|
+
if (!inLead.has(n.id)) {
|
|
720
|
+
inLead.add(n.id);
|
|
721
|
+
leadItems.push(n);
|
|
722
|
+
}
|
|
723
|
+
return false;
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
if (!items.length && !agenda) continue;
|
|
727
|
+
keep.add(day);
|
|
728
|
+
let section = this.sections.get(day);
|
|
729
|
+
let pool = this.pools.get(day);
|
|
730
|
+
if (!section || !pool) {
|
|
731
|
+
const arg = ctx.dayArg(day);
|
|
732
|
+
const heading = h(doc, "div", ["tnc-list-day", ...ctx.dayClasses(day)].join(" "), { "data-date": arg.dateStr });
|
|
733
|
+
const custom = opts.dayHeaderContent?.(arg);
|
|
734
|
+
const label = custom ?? ctx.fmt.longDate(day);
|
|
735
|
+
heading.append(opts.navLinks ? ctx.navLink(day, label, "tnc-list-day-link") : label);
|
|
736
|
+
const rows = h(doc, "div", "tnc-list-rows", { role: "group" });
|
|
737
|
+
section = h(doc, "div", "tnc-list-section", { role: "listitem", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr }, heading, rows);
|
|
738
|
+
if (agenda && opts.listAddText !== null) section.append(this.addRow(day));
|
|
739
|
+
opts.dayHeaderDidMount?.({ ...arg, el: heading });
|
|
740
|
+
pool = new ItemPool(ctx, rows);
|
|
741
|
+
this.sections.set(day, section);
|
|
742
|
+
this.pools.set(day, pool);
|
|
743
|
+
}
|
|
744
|
+
section.classList.toggle("is-empty", !items.length);
|
|
745
|
+
this.fill(pool, items, () => day);
|
|
746
|
+
if (prev ? prev.nextSibling !== section : this.body.firstChild !== section) {
|
|
747
|
+
if (prev) prev.after(section);
|
|
748
|
+
else this.body.prepend(section);
|
|
749
|
+
}
|
|
750
|
+
prev = section;
|
|
751
|
+
}
|
|
752
|
+
for (const [day, section] of this.sections) {
|
|
753
|
+
if (keep.has(day)) continue;
|
|
754
|
+
this.pools.get(day)?.clear();
|
|
755
|
+
this.pools.delete(day);
|
|
756
|
+
section.remove();
|
|
757
|
+
this.sections.delete(day);
|
|
758
|
+
}
|
|
759
|
+
this.renderLead(leadItems);
|
|
760
|
+
let empty = this.body.querySelector(":scope > .tnc-list-empty");
|
|
761
|
+
if (!keep.size && !leadItems.length) {
|
|
762
|
+
if (!empty) {
|
|
763
|
+
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
764
|
+
this.body.append(empty);
|
|
765
|
+
}
|
|
766
|
+
} else empty?.remove();
|
|
767
|
+
this.ctx.strip?.draw();
|
|
768
|
+
if (this.loader && this.sentinel) {
|
|
769
|
+
this.loader.unobserve(this.sentinel);
|
|
770
|
+
this.loader.observe(this.sentinel);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
/** Places items in a pool, keeping DOM order equal to item order. */
|
|
774
|
+
fill(pool, items, dayFor) {
|
|
775
|
+
pool.begin();
|
|
776
|
+
for (const n of items) {
|
|
777
|
+
const d = dayFor(n);
|
|
778
|
+
pool.place(n.id, { n, day: d, isStart: n.startDay === d, isEnd: n.endDay === d + 1, variant: "list", mirror: false });
|
|
779
|
+
}
|
|
780
|
+
pool.end();
|
|
781
|
+
for (const n of items) {
|
|
782
|
+
const el = pool.elementsFor(n.id)[0];
|
|
783
|
+
if (el) pool.container.append(el);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
renderLead(items) {
|
|
787
|
+
const { ctx } = this;
|
|
788
|
+
const { doc, opts } = ctx;
|
|
789
|
+
if (!items.length || opts.listLeadTitle === null) {
|
|
790
|
+
if (!this.lead) return;
|
|
791
|
+
opts.listLeadWillUnmount?.({ el: this.lead.slot });
|
|
792
|
+
this.lead.pool.clear();
|
|
793
|
+
this.lead.section.remove();
|
|
794
|
+
this.lead = null;
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (!this.lead) {
|
|
798
|
+
const title = opts.listLeadTitle;
|
|
799
|
+
const slot = h(doc, "div", "tnc-list-lead-slot", { "data-tnc-no-drag": "" });
|
|
800
|
+
const heading = h(doc, "div", "tnc-list-day tnc-list-lead-head", null, h(doc, "span", "tnc-list-lead-title", null, title), slot);
|
|
801
|
+
const rows = h(doc, "div", "tnc-list-rows", { role: "group" });
|
|
802
|
+
const section = h(doc, "div", "tnc-list-section tnc-list-lead", { role: "listitem", "aria-label": title }, heading, rows);
|
|
803
|
+
this.lead = { section, slot, pool: new ItemPool(ctx, rows) };
|
|
804
|
+
opts.listLeadDidMount?.({ el: slot });
|
|
805
|
+
}
|
|
806
|
+
if (this.body.firstChild !== this.lead.section) this.body.prepend(this.lead.section);
|
|
807
|
+
const first = ctx.days[0] ?? ctx.view.range.startDay;
|
|
808
|
+
this.fill(this.lead.pool, items, (n) => Math.max(n.startDay, first));
|
|
809
|
+
}
|
|
810
|
+
addRow(day) {
|
|
811
|
+
const { ctx } = this;
|
|
812
|
+
const label = ctx.opts.listAddText ?? "";
|
|
813
|
+
const btn = h(ctx.doc, "button", "tnc-list-add", { type: "button", "aria-label": `${label}, ${ctx.fmt.longDate(day)}` }, label);
|
|
814
|
+
btn.addEventListener("click", (ev) => ctx.opts.onListAdd?.({ ...ctx.dayArg(day), jsEvent: ev }));
|
|
815
|
+
return btn;
|
|
816
|
+
}
|
|
817
|
+
/** Brings a day's section to the top, under the strip; the first day brings the lead section too. */
|
|
818
|
+
scrollToDay(day) {
|
|
819
|
+
const section = day === this.ctx.days[0] && this.lead ? this.lead.section : this.sections.get(day);
|
|
820
|
+
if (!section) return;
|
|
821
|
+
this.ctx.strip?.measure();
|
|
822
|
+
section.scrollIntoView({ block: "start" });
|
|
823
|
+
this.ctx.strip?.setFocus(day);
|
|
824
|
+
this.pinned = true;
|
|
825
|
+
}
|
|
826
|
+
fold = () => {
|
|
827
|
+
this.pinned = false;
|
|
828
|
+
const strip = this.ctx.strip;
|
|
829
|
+
if (strip?.expanded) strip.setExpanded(false);
|
|
830
|
+
};
|
|
831
|
+
onScroll = () => {
|
|
832
|
+
if (this.frame !== null) return;
|
|
833
|
+
this.frame = this.ctx.win.requestAnimationFrame(() => {
|
|
834
|
+
this.frame = null;
|
|
835
|
+
this.followTop();
|
|
836
|
+
});
|
|
837
|
+
};
|
|
838
|
+
/** Marks the first day not hidden under the strip. */
|
|
839
|
+
followTop() {
|
|
840
|
+
const strip = this.ctx.strip;
|
|
841
|
+
if (!strip || this.pinned) return;
|
|
842
|
+
const edge = strip.el.getBoundingClientRect().bottom + 1;
|
|
843
|
+
for (const day of this.ctx.days) {
|
|
844
|
+
const section = this.sections.get(day);
|
|
845
|
+
if (section && section.getBoundingClientRect().bottom > edge) {
|
|
846
|
+
strip.setFocus(day);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
showSelection(_sel) {
|
|
852
|
+
}
|
|
853
|
+
hit(x, y) {
|
|
854
|
+
const el = this.ctx.doc.elementFromPoint(x, y);
|
|
855
|
+
const section = el?.closest(".tnc-list-section");
|
|
856
|
+
if (!section || !this.body.contains(section)) return null;
|
|
857
|
+
if (section === this.lead?.section) {
|
|
858
|
+
const item = el?.closest(".tnc-item");
|
|
859
|
+
const meta = item ? this.ctx.meta.get(item) : void 0;
|
|
860
|
+
return { kind: "day", day: meta?.day ?? this.ctx.days[0] ?? this.ctx.view.range.startDay };
|
|
861
|
+
}
|
|
862
|
+
const day = parseDay(section.dataset.date ?? "");
|
|
863
|
+
return day === null ? null : { kind: "day", day };
|
|
864
|
+
}
|
|
865
|
+
getScroll() {
|
|
866
|
+
return { top: this.scroller.scrollTop, left: 0 };
|
|
867
|
+
}
|
|
868
|
+
setScroll(s) {
|
|
869
|
+
this.scroller.scrollTop = s.top;
|
|
870
|
+
}
|
|
871
|
+
elementsFor(id) {
|
|
872
|
+
const pools = [...this.pools.values(), ...this.lead ? [this.lead.pool] : []];
|
|
873
|
+
return pools.flatMap((p) => p.elementsFor(id));
|
|
874
|
+
}
|
|
875
|
+
destroy() {
|
|
876
|
+
const { win, opts } = this.ctx;
|
|
877
|
+
win.removeEventListener("scroll", this.onScroll, { capture: true });
|
|
878
|
+
if (this.frame !== null) win.cancelAnimationFrame(this.frame);
|
|
879
|
+
this.loader?.disconnect();
|
|
880
|
+
if (this.lead) opts.listLeadWillUnmount?.({ el: this.lead.slot });
|
|
881
|
+
this.lead?.pool.clear();
|
|
882
|
+
for (const p of this.pools.values()) p.clear();
|
|
883
|
+
this.el.remove();
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
|
|
645
887
|
// src/dom/views/strip.ts
|
|
646
888
|
var SWIPE_PX = 40;
|
|
647
889
|
var DateStrip = class {
|
|
648
|
-
constructor(ctx, pick, focus) {
|
|
890
|
+
constructor(ctx, pick, focus, onShown) {
|
|
649
891
|
this.ctx = ctx;
|
|
650
892
|
this.pick = pick;
|
|
893
|
+
this.onShown = onShown;
|
|
651
894
|
const { doc, text } = ctx;
|
|
652
895
|
this.focus = focus;
|
|
653
896
|
this.shown = startOfWeek(focus, ctx.firstDay);
|
|
@@ -659,8 +902,10 @@ var DateStrip = class {
|
|
|
659
902
|
const next = h(doc, "button", "tnc-strip-nav tnc-strip-next", { type: "button", "aria-label": text.next, title: text.next }, "\u203A");
|
|
660
903
|
prev.addEventListener("click", () => this.step(-1));
|
|
661
904
|
next.addEventListener("click", () => this.step(1));
|
|
905
|
+
this.todayBtn = h(doc, "button", "tnc-strip-today", { type: "button" }, text.today);
|
|
906
|
+
this.todayBtn.addEventListener("click", (ev) => this.pick(ctx.today, ev));
|
|
662
907
|
this.slot = h(doc, "div", "tnc-strip-slot", { "data-tnc-no-drag": "" });
|
|
663
|
-
head.append(this.title, prev, next, this.slot);
|
|
908
|
+
head.append(this.title, this.todayBtn, prev, next, this.slot);
|
|
664
909
|
this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
|
|
665
910
|
this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
|
|
666
911
|
const handle = h(doc, "button", "tnc-strip-handle", { type: "button", tabindex: "-1", "aria-hidden": "true" });
|
|
@@ -677,27 +922,45 @@ var DateStrip = class {
|
|
|
677
922
|
}
|
|
678
923
|
ctx;
|
|
679
924
|
pick;
|
|
925
|
+
onShown;
|
|
680
926
|
el;
|
|
681
927
|
title;
|
|
682
928
|
dows;
|
|
683
929
|
grid;
|
|
684
930
|
slot;
|
|
931
|
+
todayBtn;
|
|
685
932
|
/** Day at the top of the list. */
|
|
686
933
|
focus;
|
|
687
934
|
/** First day of the week shown, or the month shown when expanded. */
|
|
688
935
|
shown;
|
|
689
936
|
target = null;
|
|
937
|
+
/** Days tinted as on screen besides the focus (a time grid's range). */
|
|
938
|
+
range = null;
|
|
939
|
+
shownKey = "";
|
|
690
940
|
swipe = null;
|
|
691
941
|
swallowClick = false;
|
|
692
942
|
expanded = false;
|
|
693
|
-
/** The list scrolled: mark
|
|
694
|
-
setFocus(day) {
|
|
695
|
-
|
|
696
|
-
this.focus
|
|
697
|
-
|
|
698
|
-
|
|
943
|
+
/** The list scrolled or the grid moved: mark the day and keep it in view. */
|
|
944
|
+
setFocus(day, range = this.range) {
|
|
945
|
+
const sameRange = range?.start === this.range?.start && range?.end === this.range?.end;
|
|
946
|
+
if (day === this.focus && sameRange) return;
|
|
947
|
+
this.range = range;
|
|
948
|
+
if (day !== this.focus) {
|
|
949
|
+
this.focus = day;
|
|
950
|
+
if (!this.expanded) this.shown = startOfWeek(day, this.ctx.firstDay);
|
|
951
|
+
else if (startOfMonth(day) !== this.shown) this.shown = startOfMonth(day);
|
|
952
|
+
}
|
|
699
953
|
this.draw();
|
|
700
954
|
}
|
|
955
|
+
/** Publishes the strip's height as `--tnc-strip-h`, for what sticks below it. */
|
|
956
|
+
measure() {
|
|
957
|
+
this.el.parentElement?.style.setProperty("--tnc-strip-h", `${this.el.offsetHeight}px`);
|
|
958
|
+
}
|
|
959
|
+
/** First day and end (exclusive) of the days on show. */
|
|
960
|
+
window() {
|
|
961
|
+
const days = this.days();
|
|
962
|
+
return { start: days[0], end: days[days.length - 1] + 1 };
|
|
963
|
+
}
|
|
701
964
|
setExpanded(on) {
|
|
702
965
|
if (on === this.expanded) return;
|
|
703
966
|
this.expanded = on;
|
|
@@ -735,6 +998,7 @@ var DateStrip = class {
|
|
|
735
998
|
this.title.replaceChildren(fmt.monthYear(titleDay), h(doc, "span", "tnc-strip-caret", { "aria-hidden": "true" }));
|
|
736
999
|
this.title.setAttribute("aria-expanded", String(this.expanded));
|
|
737
1000
|
this.el.classList.toggle("is-expanded", this.expanded);
|
|
1001
|
+
this.todayBtn.hidden = this.focus === ctx.today;
|
|
738
1002
|
this.dows.replaceChildren(...days.slice(0, 7).map((d) => h(doc, "span", null, null, fmt.weekdayNarrow(d))));
|
|
739
1003
|
const focused = this.grid.contains(doc.activeElement) ? doc.activeElement.dataset.date : void 0;
|
|
740
1004
|
const month = ymdOfDay(this.shown).month;
|
|
@@ -751,6 +1015,7 @@ var DateStrip = class {
|
|
|
751
1015
|
"tnc-strip-day",
|
|
752
1016
|
...ctx.dayClasses(d),
|
|
753
1017
|
d === this.focus ? "is-focus" : "",
|
|
1018
|
+
d !== this.focus && this.range && d >= this.range.start && d < this.range.end ? "is-in-range" : "",
|
|
754
1019
|
count ? "has-items" : "",
|
|
755
1020
|
this.expanded && ymdOfDay(d).month !== month ? "is-other-month" : "",
|
|
756
1021
|
d === this.target ? "is-drop-target" : ""
|
|
@@ -770,6 +1035,11 @@ var DateStrip = class {
|
|
|
770
1035
|
this.grid.replaceChildren(...cells);
|
|
771
1036
|
this.grid.setAttribute("aria-label", fmt.monthYear(titleDay));
|
|
772
1037
|
if (focused) this.grid.querySelector(`[data-date="${focused}"]`)?.focus({ preventScroll: true });
|
|
1038
|
+
const key = `${days[0]}:${days.length}`;
|
|
1039
|
+
if (key !== this.shownKey) {
|
|
1040
|
+
this.shownKey = key;
|
|
1041
|
+
this.onShown();
|
|
1042
|
+
}
|
|
773
1043
|
}
|
|
774
1044
|
onClick = (ev) => {
|
|
775
1045
|
const day = this.dayAt(ev.target);
|
|
@@ -824,205 +1094,6 @@ var DateStrip = class {
|
|
|
824
1094
|
}
|
|
825
1095
|
};
|
|
826
1096
|
|
|
827
|
-
// src/dom/views/list.ts
|
|
828
|
-
var LOAD_AHEAD = "0px 0px 600px 0px";
|
|
829
|
-
var ListView = class {
|
|
830
|
-
constructor(ctx) {
|
|
831
|
-
this.ctx = ctx;
|
|
832
|
-
const { doc, win, opts } = ctx;
|
|
833
|
-
this.el = h(doc, "div", "tnc-list");
|
|
834
|
-
this.scroller = h(doc, "div", "tnc-list-scroll");
|
|
835
|
-
this.body = h(doc, "div", "tnc-list-body", { role: "list", "aria-label": ctx.view.title });
|
|
836
|
-
this.scroller.append(this.body);
|
|
837
|
-
this.el.append(this.scroller);
|
|
838
|
-
if (!opts.listAgenda) return;
|
|
839
|
-
this.el.classList.add("tnc-list--agenda");
|
|
840
|
-
this.strip = new DateStrip(ctx, (day, ev) => this.pick(day, ev), ctx.view.range.startDay);
|
|
841
|
-
this.el.prepend(this.strip.el);
|
|
842
|
-
this.sentinel = h(doc, "div", "tnc-list-more", { "aria-hidden": "true" });
|
|
843
|
-
this.scroller.append(this.sentinel);
|
|
844
|
-
const globals = win;
|
|
845
|
-
const IO = globals.IntersectionObserver;
|
|
846
|
-
if (IO) {
|
|
847
|
-
this.loader = new IO(
|
|
848
|
-
(entries) => {
|
|
849
|
-
if (entries.some((e) => e.isIntersecting)) ctx.extendList(ctx.view.range.endDay + Math.max(7, opts.listDayCount));
|
|
850
|
-
},
|
|
851
|
-
// Auto height scrolls the page, so watch the viewport; fixed height scrolls the list.
|
|
852
|
-
{ root: opts.height === "auto" ? null : this.scroller, rootMargin: LOAD_AHEAD }
|
|
853
|
-
);
|
|
854
|
-
}
|
|
855
|
-
const RO = globals.ResizeObserver;
|
|
856
|
-
if (RO) {
|
|
857
|
-
const strip = this.strip.el;
|
|
858
|
-
this.stripSize = new RO(() => this.el.style.setProperty("--tnc-strip-h", `${strip.offsetHeight}px`));
|
|
859
|
-
this.stripSize.observe(strip);
|
|
860
|
-
}
|
|
861
|
-
win.addEventListener("scroll", this.onScroll, { capture: true, passive: true });
|
|
862
|
-
this.scroller.addEventListener("wheel", this.fold, { passive: true });
|
|
863
|
-
this.scroller.addEventListener("touchmove", this.fold, { passive: true });
|
|
864
|
-
}
|
|
865
|
-
ctx;
|
|
866
|
-
el;
|
|
867
|
-
scroller;
|
|
868
|
-
body;
|
|
869
|
-
pools = /* @__PURE__ */ new Map();
|
|
870
|
-
sections = /* @__PURE__ */ new Map();
|
|
871
|
-
strip = null;
|
|
872
|
-
sentinel = null;
|
|
873
|
-
loader = null;
|
|
874
|
-
stripSize = null;
|
|
875
|
-
frame = null;
|
|
876
|
-
/** A picked day stays marked until the list is scrolled by hand. */
|
|
877
|
-
pinned = false;
|
|
878
|
-
itemsOn(day) {
|
|
879
|
-
const { store, opts } = this.ctx;
|
|
880
|
-
const out = store.spanningIn(day, day + 1, true);
|
|
881
|
-
const seen = new Set(out.map((n) => n.id));
|
|
882
|
-
for (const n of store.timedOn(day)) if (!seen.has(n.id)) out.push(n);
|
|
883
|
-
return out.filter((n) => n.item.display !== "background" && n.item.display !== "none" && (opts.allDaySlot || !n.allDay)).sort((a, b) => Number(b.allDay) - Number(a.allDay) || this.ctx.order(a, b));
|
|
884
|
-
}
|
|
885
|
-
render(_change) {
|
|
886
|
-
const { ctx } = this;
|
|
887
|
-
const { doc, days } = ctx;
|
|
888
|
-
const agenda = !!this.strip;
|
|
889
|
-
const keep = /* @__PURE__ */ new Set();
|
|
890
|
-
let prev = null;
|
|
891
|
-
for (const day of days) {
|
|
892
|
-
const items = this.itemsOn(day);
|
|
893
|
-
if (!items.length && !agenda) continue;
|
|
894
|
-
keep.add(day);
|
|
895
|
-
let section = this.sections.get(day);
|
|
896
|
-
let pool = this.pools.get(day);
|
|
897
|
-
if (!section || !pool) {
|
|
898
|
-
const arg = ctx.dayArg(day);
|
|
899
|
-
const heading = h(doc, "div", ["tnc-list-day", ...ctx.dayClasses(day)].join(" "), { "data-date": arg.dateStr });
|
|
900
|
-
const custom = ctx.opts.dayHeaderContent?.(arg);
|
|
901
|
-
const label = custom ?? ctx.fmt.longDate(day);
|
|
902
|
-
heading.append(ctx.opts.navLinks ? ctx.navLink(day, label, "tnc-list-day-link") : label);
|
|
903
|
-
const rows2 = h(doc, "div", "tnc-list-rows", { role: "group" });
|
|
904
|
-
section = h(doc, "div", "tnc-list-section", { role: "listitem", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr }, heading, rows2);
|
|
905
|
-
ctx.opts.dayHeaderDidMount?.({ ...arg, el: heading });
|
|
906
|
-
pool = new ItemPool(ctx, rows2);
|
|
907
|
-
this.sections.set(day, section);
|
|
908
|
-
this.pools.set(day, pool);
|
|
909
|
-
}
|
|
910
|
-
section.classList.toggle("is-empty", !items.length);
|
|
911
|
-
pool.begin();
|
|
912
|
-
for (const n of items) {
|
|
913
|
-
pool.place(n.id, {
|
|
914
|
-
n,
|
|
915
|
-
day,
|
|
916
|
-
isStart: n.startDay === day,
|
|
917
|
-
isEnd: n.endDay === day + 1,
|
|
918
|
-
variant: "list",
|
|
919
|
-
mirror: false
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
pool.end();
|
|
923
|
-
const rows = pool.container;
|
|
924
|
-
for (const n of items) {
|
|
925
|
-
const el = pool.elementsFor(n.id)[0];
|
|
926
|
-
if (el) rows.append(el);
|
|
927
|
-
}
|
|
928
|
-
if (prev ? prev.nextSibling !== section : this.body.firstChild !== section) {
|
|
929
|
-
if (prev) prev.after(section);
|
|
930
|
-
else this.body.prepend(section);
|
|
931
|
-
}
|
|
932
|
-
prev = section;
|
|
933
|
-
}
|
|
934
|
-
for (const [day, section] of this.sections) {
|
|
935
|
-
if (keep.has(day)) continue;
|
|
936
|
-
this.pools.get(day)?.clear();
|
|
937
|
-
this.pools.delete(day);
|
|
938
|
-
section.remove();
|
|
939
|
-
this.sections.delete(day);
|
|
940
|
-
}
|
|
941
|
-
let empty = this.body.querySelector(":scope > .tnc-list-empty");
|
|
942
|
-
if (!keep.size) {
|
|
943
|
-
if (!empty) {
|
|
944
|
-
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
945
|
-
this.body.append(empty);
|
|
946
|
-
}
|
|
947
|
-
} else empty?.remove();
|
|
948
|
-
this.strip?.draw();
|
|
949
|
-
if (this.loader && this.sentinel) {
|
|
950
|
-
this.loader.unobserve(this.sentinel);
|
|
951
|
-
this.loader.observe(this.sentinel);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
/** A strip day: the host hears of it, and the list brings it to the top. */
|
|
955
|
-
pick(day, ev) {
|
|
956
|
-
this.ctx.dateClick(day, ev);
|
|
957
|
-
this.ctx.showListDay(day);
|
|
958
|
-
}
|
|
959
|
-
/** Brings a day's section to the top, under the strip. */
|
|
960
|
-
scrollToDay(day) {
|
|
961
|
-
const section = this.sections.get(day);
|
|
962
|
-
if (!section) return;
|
|
963
|
-
if (this.strip) this.el.style.setProperty("--tnc-strip-h", `${this.strip.el.offsetHeight}px`);
|
|
964
|
-
section.scrollIntoView({ block: "start" });
|
|
965
|
-
this.strip?.setFocus(day);
|
|
966
|
-
this.pinned = true;
|
|
967
|
-
}
|
|
968
|
-
fold = () => {
|
|
969
|
-
this.pinned = false;
|
|
970
|
-
if (this.strip?.expanded) this.strip.setExpanded(false);
|
|
971
|
-
};
|
|
972
|
-
onScroll = () => {
|
|
973
|
-
if (this.frame !== null) return;
|
|
974
|
-
this.frame = this.ctx.win.requestAnimationFrame(() => {
|
|
975
|
-
this.frame = null;
|
|
976
|
-
this.followTop();
|
|
977
|
-
});
|
|
978
|
-
};
|
|
979
|
-
/** Marks the first day not hidden under the strip. */
|
|
980
|
-
followTop() {
|
|
981
|
-
if (!this.strip || this.pinned) return;
|
|
982
|
-
const edge = this.strip.el.getBoundingClientRect().bottom + 1;
|
|
983
|
-
for (const day of this.ctx.days) {
|
|
984
|
-
const section = this.sections.get(day);
|
|
985
|
-
if (section && section.getBoundingClientRect().bottom > edge) {
|
|
986
|
-
this.strip.setFocus(day);
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
showSelection(_sel) {
|
|
992
|
-
}
|
|
993
|
-
hit(x, y) {
|
|
994
|
-
const el = this.ctx.doc.elementFromPoint(x, y);
|
|
995
|
-
const stripDay = el && this.strip ? this.strip.dayAt(el) : null;
|
|
996
|
-
this.strip?.markTarget(stripDay !== null && this.ctx.draggingId ? stripDay : null);
|
|
997
|
-
if (stripDay !== null) return { kind: "day", day: stripDay };
|
|
998
|
-
const section = el?.closest(".tnc-list-section");
|
|
999
|
-
const day = section && this.body.contains(section) ? parseDay(section.dataset.date ?? "") : null;
|
|
1000
|
-
return day === null ? null : { kind: "day", day };
|
|
1001
|
-
}
|
|
1002
|
-
dragEnded() {
|
|
1003
|
-
this.strip?.markTarget(null);
|
|
1004
|
-
}
|
|
1005
|
-
getScroll() {
|
|
1006
|
-
return { top: this.scroller.scrollTop, left: 0 };
|
|
1007
|
-
}
|
|
1008
|
-
setScroll(s) {
|
|
1009
|
-
this.scroller.scrollTop = s.top;
|
|
1010
|
-
}
|
|
1011
|
-
elementsFor(id) {
|
|
1012
|
-
return [...this.pools.values()].flatMap((p) => p.elementsFor(id));
|
|
1013
|
-
}
|
|
1014
|
-
destroy() {
|
|
1015
|
-
const { win } = this.ctx;
|
|
1016
|
-
win.removeEventListener("scroll", this.onScroll, { capture: true });
|
|
1017
|
-
if (this.frame !== null) win.cancelAnimationFrame(this.frame);
|
|
1018
|
-
this.loader?.disconnect();
|
|
1019
|
-
this.stripSize?.disconnect();
|
|
1020
|
-
this.strip?.destroy();
|
|
1021
|
-
for (const p of this.pools.values()) p.clear();
|
|
1022
|
-
this.el.remove();
|
|
1023
|
-
}
|
|
1024
|
-
};
|
|
1025
|
-
|
|
1026
1097
|
// src/dom/views/timegrid.ts
|
|
1027
1098
|
var BAR_GAP2 = 2;
|
|
1028
1099
|
var TimeGridView = class {
|
|
@@ -1241,17 +1312,22 @@ var TimeGridView = class {
|
|
|
1241
1312
|
const pool = this.allDayPool;
|
|
1242
1313
|
pool.begin();
|
|
1243
1314
|
const hidden = new Array(days.length).fill(0);
|
|
1315
|
+
const previewLevels = new Array(days.length).fill(0);
|
|
1244
1316
|
let maxLevel = -1;
|
|
1245
1317
|
for (const p of row.placements) {
|
|
1246
1318
|
let level = p.level;
|
|
1247
1319
|
let room = Infinity;
|
|
1248
1320
|
for (let c = p.startCol; c < p.endCol; c++) room = Math.min(room, visibleLevels(c));
|
|
1249
1321
|
if (level >= room) {
|
|
1250
|
-
if (p.item.id !== ctx.draggingId) {
|
|
1322
|
+
if (!p.item.item.preview && p.item.id !== ctx.draggingId) {
|
|
1251
1323
|
for (let c = p.startCol; c < p.endCol; c++) hidden[c]++;
|
|
1252
1324
|
continue;
|
|
1253
1325
|
}
|
|
1254
|
-
|
|
1326
|
+
if (p.item.item.preview) {
|
|
1327
|
+
level = 0;
|
|
1328
|
+
for (let c = p.startCol; c < p.endCol; c++) level = Math.max(level, visibleLevels(c) + 1, previewLevels[c]);
|
|
1329
|
+
for (let c = p.startCol; c < p.endCol; c++) previewLevels[c] = level + 1;
|
|
1330
|
+
} else level = Math.max(0, room - 1);
|
|
1255
1331
|
}
|
|
1256
1332
|
maxLevel = Math.max(maxLevel, level);
|
|
1257
1333
|
const el = pool.place(p.item.id, {
|
|
@@ -1339,7 +1415,8 @@ var TimeGridView = class {
|
|
|
1339
1415
|
const hiddenByCluster = /* @__PURE__ */ new Map();
|
|
1340
1416
|
pool.begin();
|
|
1341
1417
|
for (const p of placements) {
|
|
1342
|
-
|
|
1418
|
+
const preview = p.item.item.preview || p.item.id === ctx.draggingId;
|
|
1419
|
+
if (!preview && withMore && maxStack > 0 && p.cols > maxStack && p.col >= maxStack - 1) {
|
|
1343
1420
|
const rec = hiddenByCluster.get(p.cluster) ?? { count: 0, start: p.startMin, end: p.endMin, items: [] };
|
|
1344
1421
|
rec.count++;
|
|
1345
1422
|
rec.start = Math.min(rec.start, p.startMin);
|
|
@@ -1348,7 +1425,7 @@ var TimeGridView = class {
|
|
|
1348
1425
|
hiddenByCluster.set(p.cluster, rec);
|
|
1349
1426
|
continue;
|
|
1350
1427
|
}
|
|
1351
|
-
const cols = withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
|
|
1428
|
+
const cols = !preview && withMore && maxStack > 0 ? Math.min(p.cols, maxStack) : p.cols;
|
|
1352
1429
|
const span = Math.min(p.span, cols - p.col);
|
|
1353
1430
|
const left = p.col / cols;
|
|
1354
1431
|
let width = span / cols;
|
|
@@ -2228,6 +2305,9 @@ var Calendar = class {
|
|
|
2228
2305
|
slot = { min: 0, max: MINUTES_PER_DAY, duration: 30, snap: 30, scroll: 480 };
|
|
2229
2306
|
draggingId = null;
|
|
2230
2307
|
dragRow = null;
|
|
2308
|
+
strip = null;
|
|
2309
|
+
stripSize = null;
|
|
2310
|
+
rebuilding = false;
|
|
2231
2311
|
toolbarEl;
|
|
2232
2312
|
viewHost;
|
|
2233
2313
|
current = null;
|
|
@@ -2441,7 +2521,7 @@ var Calendar = class {
|
|
|
2441
2521
|
}
|
|
2442
2522
|
/** What is under a client point: a time slot, a day, or a timeline row and date. Null outside the view. */
|
|
2443
2523
|
hitTest(clientX, clientY) {
|
|
2444
|
-
return this.
|
|
2524
|
+
return this.hitAt(clientX, clientY);
|
|
2445
2525
|
}
|
|
2446
2526
|
/** Item elements currently rendered for an id. */
|
|
2447
2527
|
elementsFor(id) {
|
|
@@ -2483,6 +2563,9 @@ var Calendar = class {
|
|
|
2483
2563
|
this.unbindEvents();
|
|
2484
2564
|
this.current?.destroy();
|
|
2485
2565
|
this.current = null;
|
|
2566
|
+
this.stripSize?.disconnect();
|
|
2567
|
+
this.strip?.destroy();
|
|
2568
|
+
this.strip = null;
|
|
2486
2569
|
this.root.remove();
|
|
2487
2570
|
}
|
|
2488
2571
|
/* ========================================================== view context */
|
|
@@ -2513,7 +2596,7 @@ var Calendar = class {
|
|
|
2513
2596
|
return out;
|
|
2514
2597
|
}
|
|
2515
2598
|
isEditable(n, what) {
|
|
2516
|
-
if (n.id === EXTERNAL_ID || this.opts.view === "year") return false;
|
|
2599
|
+
if (n.id === EXTERNAL_ID || n.item.preview || this.opts.view === "year") return false;
|
|
2517
2600
|
if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
|
|
2518
2601
|
const it = n.item;
|
|
2519
2602
|
const base = it.editable ?? this.opts.editable;
|
|
@@ -2539,6 +2622,7 @@ var Calendar = class {
|
|
|
2539
2622
|
createItemEl(meta) {
|
|
2540
2623
|
const { n, variant } = meta;
|
|
2541
2624
|
const it = n.item;
|
|
2625
|
+
const preview = meta.mirror || it.preview === true;
|
|
2542
2626
|
const el = h(this.doc, "div", "tnc-item");
|
|
2543
2627
|
el.classList.add(`tnc-item--${variant}`);
|
|
2544
2628
|
if (it.kind) el.classList.add(`tnc-k-${it.kind.replace(/[^\w-]/g, "-")}`);
|
|
@@ -2548,12 +2632,12 @@ var Calendar = class {
|
|
|
2548
2632
|
if (!meta.isStart) el.classList.add("is-cont-start");
|
|
2549
2633
|
if (!meta.isEnd) el.classList.add("is-cont-end");
|
|
2550
2634
|
if (n.allDay) el.classList.add("is-all-day");
|
|
2551
|
-
if (
|
|
2635
|
+
if (preview || n.id === EXTERNAL_ID) el.classList.add("is-mirror");
|
|
2552
2636
|
if (this.conflicts.has(n.id)) el.classList.add("is-conflict");
|
|
2553
2637
|
const indicator = variant === "indicator";
|
|
2554
|
-
if (!
|
|
2638
|
+
if (!preview && !indicator && (this.isEditable(n, "start") || this.isEditable(n, "duration"))) el.classList.add("is-draggable");
|
|
2555
2639
|
if (this.draggingId === n.id) el.classList.add("is-dragging");
|
|
2556
|
-
if (this.selectedIds.has(n.id) && !
|
|
2640
|
+
if (this.selectedIds.has(n.id) && !preview) el.classList.add("is-selected");
|
|
2557
2641
|
el.classList.add(...classList(it.className));
|
|
2558
2642
|
const arg = this.itemArg(el, meta);
|
|
2559
2643
|
el.classList.add(...classList(this.opts.itemClassNames?.(arg)));
|
|
@@ -2564,7 +2648,7 @@ var Calendar = class {
|
|
|
2564
2648
|
if (it.backgroundColor) el.style.setProperty("--tnc-bg", it.backgroundColor);
|
|
2565
2649
|
if (it.borderColor) el.style.setProperty("--tnc-border", it.borderColor);
|
|
2566
2650
|
if (it.textColor) el.style.setProperty("--tnc-fg", it.textColor);
|
|
2567
|
-
if (indicator) el.setAttribute("aria-hidden", "true");
|
|
2651
|
+
if (indicator || preview) el.setAttribute("aria-hidden", "true");
|
|
2568
2652
|
else if (it.display !== "background") {
|
|
2569
2653
|
el.tabIndex = 0;
|
|
2570
2654
|
el.setAttribute("role", "button");
|
|
@@ -2581,7 +2665,7 @@ var Calendar = class {
|
|
|
2581
2665
|
} else {
|
|
2582
2666
|
el.append(this.defaultContent(meta));
|
|
2583
2667
|
}
|
|
2584
|
-
if (variant !== "list" && !indicator && it.display !== "background") this.addHandles(el, meta);
|
|
2668
|
+
if (!preview && variant !== "list" && !indicator && it.display !== "background") this.addHandles(el, meta);
|
|
2585
2669
|
this.opts.itemDidMount?.(arg);
|
|
2586
2670
|
return el;
|
|
2587
2671
|
}
|
|
@@ -2590,7 +2674,7 @@ var Calendar = class {
|
|
|
2590
2674
|
if (time) this.fillTime(time, meta.n, meta.variant, meta.day);
|
|
2591
2675
|
el.classList.toggle("is-dragging", this.draggingId === meta.n.id);
|
|
2592
2676
|
el.classList.toggle("is-conflict", this.conflicts.has(meta.n.id));
|
|
2593
|
-
el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror);
|
|
2677
|
+
el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror && !meta.n.item.preview);
|
|
2594
2678
|
}
|
|
2595
2679
|
selectedSet() {
|
|
2596
2680
|
const s = this.opts.selected;
|
|
@@ -2600,7 +2684,7 @@ var Calendar = class {
|
|
|
2600
2684
|
this.selectedIds = this.selectedSet();
|
|
2601
2685
|
for (const el of this.viewHost.querySelectorAll(".tnc-item")) {
|
|
2602
2686
|
const meta = this.meta.get(el);
|
|
2603
|
-
if (meta) el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror);
|
|
2687
|
+
if (meta) el.classList.toggle("is-selected", this.selectedIds.has(meta.n.id) && !meta.mirror && !meta.n.item.preview);
|
|
2604
2688
|
}
|
|
2605
2689
|
}
|
|
2606
2690
|
destroyItemEl(el) {
|
|
@@ -2751,12 +2835,18 @@ var Calendar = class {
|
|
|
2751
2835
|
const snap = Math.max(1, parseDurationMinutes(this.opts.snapDuration ?? null, duration));
|
|
2752
2836
|
this.slot = { min, max, duration, snap, scroll: parseDurationMinutes(this.opts.scrollTime, 480) };
|
|
2753
2837
|
if (navigated) this.listEnd = null;
|
|
2754
|
-
const
|
|
2838
|
+
const prev = this.view;
|
|
2839
|
+
this.applyRange();
|
|
2755
2840
|
this.conflicts = dependencyConflicts(this.store);
|
|
2756
2841
|
this.root.className = "tnc";
|
|
2757
2842
|
this.root.classList.add(`tnc--${this.opts.view}`, `tnc--height-${this.opts.height}`);
|
|
2758
2843
|
if (!this.opts.timeGrid) this.root.classList.add("tnc--no-time-grid");
|
|
2759
2844
|
this.renderToolbar();
|
|
2845
|
+
this.rebuilding = true;
|
|
2846
|
+
this.syncStrip(navigated);
|
|
2847
|
+
this.rebuilding = false;
|
|
2848
|
+
this.view.dataRange = this.dataRangeFor(this.view.range);
|
|
2849
|
+
const rangeChanged = rangeMoved(prev, this.view);
|
|
2760
2850
|
this.current?.destroy();
|
|
2761
2851
|
this.current = this.createView();
|
|
2762
2852
|
this.viewHost.replaceChildren(this.current.el);
|
|
@@ -2769,7 +2859,7 @@ var Calendar = class {
|
|
|
2769
2859
|
else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
2770
2860
|
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
2771
2861
|
}
|
|
2772
|
-
/** Sets the visible days and view info from the anchor
|
|
2862
|
+
/** Sets the visible days and view info from the anchor. */
|
|
2773
2863
|
applyRange() {
|
|
2774
2864
|
const { startDay, endDay } = this.computeRange();
|
|
2775
2865
|
const hidden = new Set(this.opts.hiddenDays);
|
|
@@ -2787,10 +2877,74 @@ var Calendar = class {
|
|
|
2787
2877
|
start: new Date(toInstant(startDay, 0, this.zone)),
|
|
2788
2878
|
end: new Date(toInstant(endDay, 0, this.zone))
|
|
2789
2879
|
};
|
|
2790
|
-
|
|
2791
|
-
this.view = { type: this.opts.view, range, anchor: this.anchor, title: "" };
|
|
2880
|
+
this.view = { type: this.opts.view, range, anchor: this.anchor, title: "", dataRange: this.dataRangeFor(range) };
|
|
2792
2881
|
this.view.title = this.computeTitle(startDay, endDay);
|
|
2793
|
-
|
|
2882
|
+
}
|
|
2883
|
+
/** The range, run on to the end of the strip's days so their dots are right. */
|
|
2884
|
+
dataRangeFor(range) {
|
|
2885
|
+
const end = this.strip ? Math.max(range.endDay, this.strip.window().end) : range.endDay;
|
|
2886
|
+
if (end === range.endDay) return range;
|
|
2887
|
+
return { ...range, endDay: end, end: new Date(toInstant(end, 0, this.zone)) };
|
|
2888
|
+
}
|
|
2889
|
+
/** Adds, updates or removes the date strip for the current view. */
|
|
2890
|
+
syncStrip(navigated) {
|
|
2891
|
+
const { view, listAgenda, dateStrip } = this.opts;
|
|
2892
|
+
const list = view === "list";
|
|
2893
|
+
const want = list ? listAgenda : dateStrip && (view === "day" || view === "days" || view === "week");
|
|
2894
|
+
if (!want) {
|
|
2895
|
+
if (!this.strip) return;
|
|
2896
|
+
this.stripSize?.disconnect();
|
|
2897
|
+
this.stripSize = null;
|
|
2898
|
+
this.strip.destroy();
|
|
2899
|
+
this.strip = null;
|
|
2900
|
+
this.root.style.removeProperty("--tnc-strip-h");
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
this.root.classList.add("tnc--has-strip");
|
|
2904
|
+
const { startDay, endDay } = this.view.range;
|
|
2905
|
+
if (!this.strip) {
|
|
2906
|
+
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown());
|
|
2907
|
+
this.strip = strip;
|
|
2908
|
+
this.root.insertBefore(strip.el, this.viewHost);
|
|
2909
|
+
const RO = this.win.ResizeObserver;
|
|
2910
|
+
if (RO) {
|
|
2911
|
+
this.stripSize = new RO(() => strip.measure());
|
|
2912
|
+
this.stripSize.observe(strip.el);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
if (!list) this.strip.setFocus(startDay, { start: startDay, end: endDay });
|
|
2916
|
+
else if (navigated) this.strip.setFocus(startDay, null);
|
|
2917
|
+
this.strip.draw();
|
|
2918
|
+
}
|
|
2919
|
+
pickStripDay(day, ev) {
|
|
2920
|
+
this.dateClick(day, ev);
|
|
2921
|
+
if (this.agendaList()) {
|
|
2922
|
+
this.showListDay(day);
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
const { startDay, endDay } = this.view.range;
|
|
2926
|
+
if (this.opts.view === "week" && day >= startDay && day < endDay) return;
|
|
2927
|
+
this.anchor = day;
|
|
2928
|
+
this.rebuild(true);
|
|
2929
|
+
}
|
|
2930
|
+
/** The strip moved to other days: load theirs too. */
|
|
2931
|
+
stripShown() {
|
|
2932
|
+
if (!this.strip || this.rebuilding || !this.view) return;
|
|
2933
|
+
const next = this.dataRangeFor(this.view.range);
|
|
2934
|
+
if (next.endDay === this.view.dataRange.endDay) return;
|
|
2935
|
+
this.view = { ...this.view, dataRange: next };
|
|
2936
|
+
this.opts.onRangeChange?.(this.view);
|
|
2937
|
+
}
|
|
2938
|
+
/** What is under a point, the date strip included. */
|
|
2939
|
+
hitAt(x, y) {
|
|
2940
|
+
const strip = this.strip;
|
|
2941
|
+
if (strip) {
|
|
2942
|
+
const el = this.doc.elementFromPoint(x, y);
|
|
2943
|
+
const day = el ? strip.dayAt(el) : null;
|
|
2944
|
+
strip.markTarget(day !== null && this.draggingId ? day : null);
|
|
2945
|
+
if (day !== null) return { kind: "day", day };
|
|
2946
|
+
}
|
|
2947
|
+
return this.current?.hit(x, y) ?? null;
|
|
2794
2948
|
}
|
|
2795
2949
|
agendaList() {
|
|
2796
2950
|
return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
|
|
@@ -2803,6 +2957,7 @@ var Calendar = class {
|
|
|
2803
2957
|
this.listEnd = end;
|
|
2804
2958
|
this.applyRange();
|
|
2805
2959
|
this.current?.render(null);
|
|
2960
|
+
this.strip?.draw();
|
|
2806
2961
|
this.opts.onRangeChange?.(this.view);
|
|
2807
2962
|
}
|
|
2808
2963
|
showListDay(day) {
|
|
@@ -2924,7 +3079,7 @@ var Calendar = class {
|
|
|
2924
3079
|
day: meta.day,
|
|
2925
3080
|
isStart: meta.isStart,
|
|
2926
3081
|
isEnd: meta.isEnd,
|
|
2927
|
-
isMirror: meta.mirror,
|
|
3082
|
+
isMirror: meta.mirror || meta.n.item.preview === true,
|
|
2928
3083
|
variant: meta.variant,
|
|
2929
3084
|
timeText: meta.n.allDay ? "" : end ? `${start} \u2013 ${end}` : start
|
|
2930
3085
|
};
|
|
@@ -3069,7 +3224,7 @@ var Calendar = class {
|
|
|
3069
3224
|
}
|
|
3070
3225
|
itemElFrom(target) {
|
|
3071
3226
|
const el = target?.closest?.(".tnc-item");
|
|
3072
|
-
return el && this.root.contains(el) && this.meta.has(el) ? el : null;
|
|
3227
|
+
return el && this.root.contains(el) && this.meta.has(el) && !this.meta.get(el)?.n.item.preview ? el : null;
|
|
3073
3228
|
}
|
|
3074
3229
|
onDocPointerDown = (ev) => {
|
|
3075
3230
|
const t = ev.target;
|
|
@@ -3200,7 +3355,7 @@ var Calendar = class {
|
|
|
3200
3355
|
};
|
|
3201
3356
|
/** Proposal for the current pointer position. */
|
|
3202
3357
|
track(s, ev) {
|
|
3203
|
-
const hit = this.
|
|
3358
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
3204
3359
|
if (s.mode === "external") {
|
|
3205
3360
|
this.trackExternal(s, hit);
|
|
3206
3361
|
return;
|
|
@@ -3326,7 +3481,7 @@ var Calendar = class {
|
|
|
3326
3481
|
return this.dayProposal(hit.day, hit.day + this.opts.defaultAllDayDays);
|
|
3327
3482
|
}
|
|
3328
3483
|
if (hit.kind === "day") {
|
|
3329
|
-
const dd = hit.day - (grab.kind === "
|
|
3484
|
+
const dd = hit.day - (grab.kind === "timeline" ? hit.day : grab.day);
|
|
3330
3485
|
if (o.allDay) return this.dayProposal(o.startDay + dd, o.startDay + dd + days);
|
|
3331
3486
|
const startMs2 = this.civilToMs(this.civil(o.startMs) + dd * MINUTES_PER_DAY);
|
|
3332
3487
|
const endMs = this.civilToMs(this.civil(o.endMs) + dd * MINUTES_PER_DAY);
|
|
@@ -3461,7 +3616,7 @@ var Calendar = class {
|
|
|
3461
3616
|
this.draggingId = null;
|
|
3462
3617
|
this.dragRow = null;
|
|
3463
3618
|
if (draggedId) for (const el of this.elementsFor(draggedId)) el.classList.remove("is-dragging");
|
|
3464
|
-
this.
|
|
3619
|
+
this.strip?.markTarget(null);
|
|
3465
3620
|
if (!s.active) return;
|
|
3466
3621
|
if (s.mode === "select") {
|
|
3467
3622
|
if (cancel) this.unselectSilently();
|
|
@@ -3758,6 +3913,9 @@ function scrollerOf(el, axis) {
|
|
|
3758
3913
|
}
|
|
3759
3914
|
return doc.scrollingElement;
|
|
3760
3915
|
}
|
|
3916
|
+
function rangeMoved(prev, next) {
|
|
3917
|
+
return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.endDay !== next.dataRange.endDay;
|
|
3918
|
+
}
|
|
3761
3919
|
|
|
3762
3920
|
export {
|
|
3763
3921
|
Formatter,
|