@tasknotes/calendar 0.1.1 → 0.1.3
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 +21 -0
- package/README.md +2 -1
- package/dist/calendar.css +229 -0
- package/dist/cjs/index.cjs +365 -30
- package/dist/cjs/react.cjs +365 -30
- package/dist/esm/{chunk-6RRDNLZM.js → chunk-XHPZ7K4W.js} +365 -30
- package/dist/esm/index.js +1 -1
- package/dist/esm/react.js +1 -1
- package/dist/types/dom/calendar.d.cts +8 -0
- package/dist/types/dom/calendar.d.ts +8 -0
- package/dist/types/dom/context.d.cts +8 -0
- package/dist/types/dom/context.d.ts +8 -0
- package/dist/types/dom/options.d.cts +19 -0
- package/dist/types/dom/options.d.ts +19 -0
- package/dist/types/dom/views/list.d.cts +23 -2
- package/dist/types/dom/views/list.d.ts +23 -2
- package/dist/types/dom/views/strip.d.cts +43 -0
- package/dist/types/dom/views/strip.d.ts +43 -0
- package/package.json +1 -1
|
@@ -164,6 +164,7 @@ function defaultOptions() {
|
|
|
164
164
|
dayCount: 3,
|
|
165
165
|
listDayCount: 7,
|
|
166
166
|
listStart: "date",
|
|
167
|
+
listAgenda: false,
|
|
167
168
|
monthDisplay: "full",
|
|
168
169
|
timeZone: "local",
|
|
169
170
|
locale: void 0,
|
|
@@ -213,6 +214,7 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
213
214
|
"dayCount",
|
|
214
215
|
"listDayCount",
|
|
215
216
|
"listStart",
|
|
217
|
+
"listAgenda",
|
|
216
218
|
"monthDisplay",
|
|
217
219
|
"timeZone",
|
|
218
220
|
"locale",
|
|
@@ -640,15 +642,225 @@ var DayGridView = class {
|
|
|
640
642
|
}
|
|
641
643
|
};
|
|
642
644
|
|
|
645
|
+
// src/dom/views/strip.ts
|
|
646
|
+
var SWIPE_PX = 40;
|
|
647
|
+
var DateStrip = class {
|
|
648
|
+
constructor(ctx, pick, focus) {
|
|
649
|
+
this.ctx = ctx;
|
|
650
|
+
this.pick = pick;
|
|
651
|
+
const { doc, text } = ctx;
|
|
652
|
+
this.focus = focus;
|
|
653
|
+
this.shown = startOfWeek(focus, ctx.firstDay);
|
|
654
|
+
this.el = h(doc, "div", "tnc-strip");
|
|
655
|
+
const head = h(doc, "div", "tnc-strip-head");
|
|
656
|
+
this.title = h(doc, "button", "tnc-strip-title", { type: "button", "aria-expanded": "false" });
|
|
657
|
+
this.title.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
658
|
+
const prev = h(doc, "button", "tnc-strip-nav tnc-strip-prev", { type: "button", "aria-label": text.prev, title: text.prev }, "\u2039");
|
|
659
|
+
const next = h(doc, "button", "tnc-strip-nav tnc-strip-next", { type: "button", "aria-label": text.next, title: text.next }, "\u203A");
|
|
660
|
+
prev.addEventListener("click", () => this.step(-1));
|
|
661
|
+
next.addEventListener("click", () => this.step(1));
|
|
662
|
+
this.slot = h(doc, "div", "tnc-strip-slot", { "data-tnc-no-drag": "" });
|
|
663
|
+
head.append(this.title, prev, next, this.slot);
|
|
664
|
+
this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
|
|
665
|
+
this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
|
|
666
|
+
const handle = h(doc, "button", "tnc-strip-handle", { type: "button", tabindex: "-1", "aria-hidden": "true" });
|
|
667
|
+
handle.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
668
|
+
this.el.append(head, this.dows, this.grid, handle);
|
|
669
|
+
this.grid.addEventListener("click", this.onClick);
|
|
670
|
+
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
671
|
+
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
672
|
+
this.el.addEventListener("pointerup", this.onPointerUp);
|
|
673
|
+
this.el.addEventListener("pointercancel", this.onPointerCancel);
|
|
674
|
+
this.el.addEventListener("click", this.onClickCapture, true);
|
|
675
|
+
this.draw();
|
|
676
|
+
ctx.opts.stripSlotDidMount?.({ el: this.slot });
|
|
677
|
+
}
|
|
678
|
+
ctx;
|
|
679
|
+
pick;
|
|
680
|
+
el;
|
|
681
|
+
title;
|
|
682
|
+
dows;
|
|
683
|
+
grid;
|
|
684
|
+
slot;
|
|
685
|
+
/** Day at the top of the list. */
|
|
686
|
+
focus;
|
|
687
|
+
/** First day of the week shown, or the month shown when expanded. */
|
|
688
|
+
shown;
|
|
689
|
+
target = null;
|
|
690
|
+
swipe = null;
|
|
691
|
+
swallowClick = false;
|
|
692
|
+
expanded = false;
|
|
693
|
+
/** The list scrolled: mark its top day and keep it in view. */
|
|
694
|
+
setFocus(day) {
|
|
695
|
+
if (day === this.focus) return;
|
|
696
|
+
this.focus = day;
|
|
697
|
+
if (!this.expanded) this.shown = startOfWeek(day, this.ctx.firstDay);
|
|
698
|
+
else if (startOfMonth(day) !== this.shown) this.shown = startOfMonth(day);
|
|
699
|
+
this.draw();
|
|
700
|
+
}
|
|
701
|
+
setExpanded(on) {
|
|
702
|
+
if (on === this.expanded) return;
|
|
703
|
+
this.expanded = on;
|
|
704
|
+
this.shown = on ? startOfMonth(this.focus) : startOfWeek(this.focus, this.ctx.firstDay);
|
|
705
|
+
this.draw();
|
|
706
|
+
}
|
|
707
|
+
/** Highlights the day a dragged item would land on. */
|
|
708
|
+
markTarget(day) {
|
|
709
|
+
if (day === this.target) return;
|
|
710
|
+
this.target = day;
|
|
711
|
+
for (const cell of this.grid.querySelectorAll(".tnc-strip-day"))
|
|
712
|
+
cell.classList.toggle("is-drop-target", day !== null && cell.dataset.date === this.ctx.dayArg(day).dateStr);
|
|
713
|
+
}
|
|
714
|
+
dayAt(el) {
|
|
715
|
+
const cell = el.closest(".tnc-strip-day");
|
|
716
|
+
return cell && this.grid.contains(cell) ? parseDay(cell.dataset.date ?? "") : null;
|
|
717
|
+
}
|
|
718
|
+
step(dir) {
|
|
719
|
+
this.shown = this.expanded ? addMonths(this.shown, dir) : this.shown + 7 * dir;
|
|
720
|
+
this.draw();
|
|
721
|
+
}
|
|
722
|
+
days() {
|
|
723
|
+
if (!this.expanded) return Array.from({ length: 7 }, (_, i) => this.shown + i);
|
|
724
|
+
const first = startOfWeek(this.shown, this.ctx.firstDay);
|
|
725
|
+
const end = addMonths(this.shown, 1);
|
|
726
|
+
const weeks = Math.ceil((end - first) / 7);
|
|
727
|
+
return Array.from({ length: weeks * 7 }, (_, i) => first + i);
|
|
728
|
+
}
|
|
729
|
+
/** Redraws the cells; also called on every list render to refresh the dots. */
|
|
730
|
+
draw() {
|
|
731
|
+
const { ctx } = this;
|
|
732
|
+
const { doc, fmt, store } = ctx;
|
|
733
|
+
const days = this.days();
|
|
734
|
+
const titleDay = this.expanded ? this.shown : days.includes(this.focus) ? this.focus : this.shown + 3;
|
|
735
|
+
this.title.replaceChildren(fmt.monthYear(titleDay), h(doc, "span", "tnc-strip-caret", { "aria-hidden": "true" }));
|
|
736
|
+
this.title.setAttribute("aria-expanded", String(this.expanded));
|
|
737
|
+
this.el.classList.toggle("is-expanded", this.expanded);
|
|
738
|
+
this.dows.replaceChildren(...days.slice(0, 7).map((d) => h(doc, "span", null, null, fmt.weekdayNarrow(d))));
|
|
739
|
+
const focused = this.grid.contains(doc.activeElement) ? doc.activeElement.dataset.date : void 0;
|
|
740
|
+
const month = ymdOfDay(this.shown).month;
|
|
741
|
+
const tabDay = days.includes(this.focus) ? this.focus : days[0];
|
|
742
|
+
const cells = days.map((d) => {
|
|
743
|
+
const arg = ctx.dayArg(d);
|
|
744
|
+
const count = store.inRange(d, d + 1).filter((n) => n.item.display !== "background" && n.item.display !== "none").length;
|
|
745
|
+
const first = ymdOfDay(d).day === 1 && d !== days[0] && !this.expanded;
|
|
746
|
+
const label = fmt.longDate(d);
|
|
747
|
+
const cell = h(
|
|
748
|
+
doc,
|
|
749
|
+
"button",
|
|
750
|
+
[
|
|
751
|
+
"tnc-strip-day",
|
|
752
|
+
...ctx.dayClasses(d),
|
|
753
|
+
d === this.focus ? "is-focus" : "",
|
|
754
|
+
count ? "has-items" : "",
|
|
755
|
+
this.expanded && ymdOfDay(d).month !== month ? "is-other-month" : "",
|
|
756
|
+
d === this.target ? "is-drop-target" : ""
|
|
757
|
+
].filter(Boolean).join(" "),
|
|
758
|
+
{
|
|
759
|
+
type: "button",
|
|
760
|
+
"data-date": arg.dateStr,
|
|
761
|
+
"aria-label": count ? `${label}, ${ctx.text.itemCount(count)}` : label,
|
|
762
|
+
"aria-pressed": String(d === this.focus),
|
|
763
|
+
tabindex: d === tabDay ? "0" : "-1"
|
|
764
|
+
}
|
|
765
|
+
);
|
|
766
|
+
if (first) cell.append(h(doc, "span", "tnc-strip-month", { "aria-hidden": "true" }, fmt.monthShort(d)));
|
|
767
|
+
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d)), h(doc, "span", "tnc-strip-dot", { "aria-hidden": "true" }));
|
|
768
|
+
return cell;
|
|
769
|
+
});
|
|
770
|
+
this.grid.replaceChildren(...cells);
|
|
771
|
+
this.grid.setAttribute("aria-label", fmt.monthYear(titleDay));
|
|
772
|
+
if (focused) this.grid.querySelector(`[data-date="${focused}"]`)?.focus({ preventScroll: true });
|
|
773
|
+
}
|
|
774
|
+
onClick = (ev) => {
|
|
775
|
+
const day = this.dayAt(ev.target);
|
|
776
|
+
if (day === null) return;
|
|
777
|
+
if (this.expanded) this.setExpanded(false);
|
|
778
|
+
this.pick(day, ev);
|
|
779
|
+
};
|
|
780
|
+
onKeyDown = (ev) => {
|
|
781
|
+
const day = this.dayAt(ev.target);
|
|
782
|
+
if (day === null) return;
|
|
783
|
+
const move = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[ev.key];
|
|
784
|
+
if (move === void 0) return;
|
|
785
|
+
ev.preventDefault();
|
|
786
|
+
const next = day + move;
|
|
787
|
+
const days = this.days();
|
|
788
|
+
if (next < days[0] || next > days[days.length - 1]) {
|
|
789
|
+
this.shown = this.expanded ? startOfMonth(next) : startOfWeek(next, this.ctx.firstDay);
|
|
790
|
+
this.draw();
|
|
791
|
+
}
|
|
792
|
+
const cell = this.grid.querySelector(`[data-date="${this.ctx.dayArg(next).dateStr}"]`);
|
|
793
|
+
if (!cell) return;
|
|
794
|
+
for (const c of this.grid.querySelectorAll("[tabindex='0']")) c.setAttribute("tabindex", "-1");
|
|
795
|
+
cell.setAttribute("tabindex", "0");
|
|
796
|
+
cell.focus();
|
|
797
|
+
};
|
|
798
|
+
onPointerDown = (ev) => {
|
|
799
|
+
if (ev.button !== 0 || this.ctx.draggingId) return;
|
|
800
|
+
this.swipe = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
|
|
801
|
+
};
|
|
802
|
+
onPointerUp = (ev) => {
|
|
803
|
+
const s = this.swipe;
|
|
804
|
+
this.swipe = null;
|
|
805
|
+
if (!s || s.id !== ev.pointerId) return;
|
|
806
|
+
const dx = ev.clientX - s.x;
|
|
807
|
+
if (Math.abs(dx) < SWIPE_PX || Math.abs(dx) < Math.abs(ev.clientY - s.y)) return;
|
|
808
|
+
this.swallowClick = true;
|
|
809
|
+
this.step(dx < 0 ? 1 : -1);
|
|
810
|
+
};
|
|
811
|
+
onPointerCancel = () => {
|
|
812
|
+
this.swipe = null;
|
|
813
|
+
};
|
|
814
|
+
/** A swipe that ends on a day is not a pick. */
|
|
815
|
+
onClickCapture = (ev) => {
|
|
816
|
+
if (!this.swallowClick) return;
|
|
817
|
+
this.swallowClick = false;
|
|
818
|
+
ev.stopPropagation();
|
|
819
|
+
ev.preventDefault();
|
|
820
|
+
};
|
|
821
|
+
destroy() {
|
|
822
|
+
this.ctx.opts.stripSlotWillUnmount?.({ el: this.slot });
|
|
823
|
+
this.el.remove();
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
|
|
643
827
|
// src/dom/views/list.ts
|
|
828
|
+
var LOAD_AHEAD = "0px 0px 600px 0px";
|
|
644
829
|
var ListView = class {
|
|
645
830
|
constructor(ctx) {
|
|
646
831
|
this.ctx = ctx;
|
|
647
|
-
|
|
648
|
-
this.
|
|
649
|
-
this.
|
|
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 });
|
|
650
836
|
this.scroller.append(this.body);
|
|
651
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 });
|
|
652
864
|
}
|
|
653
865
|
ctx;
|
|
654
866
|
el;
|
|
@@ -656,6 +868,13 @@ var ListView = class {
|
|
|
656
868
|
body;
|
|
657
869
|
pools = /* @__PURE__ */ new Map();
|
|
658
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;
|
|
659
878
|
itemsOn(day) {
|
|
660
879
|
const { store, opts } = this.ctx;
|
|
661
880
|
const out = store.spanningIn(day, day + 1, true);
|
|
@@ -666,11 +885,12 @@ var ListView = class {
|
|
|
666
885
|
render(_change) {
|
|
667
886
|
const { ctx } = this;
|
|
668
887
|
const { doc, days } = ctx;
|
|
888
|
+
const agenda = !!this.strip;
|
|
669
889
|
const keep = /* @__PURE__ */ new Set();
|
|
670
890
|
let prev = null;
|
|
671
891
|
for (const day of days) {
|
|
672
892
|
const items = this.itemsOn(day);
|
|
673
|
-
if (!items.length) continue;
|
|
893
|
+
if (!items.length && !agenda) continue;
|
|
674
894
|
keep.add(day);
|
|
675
895
|
let section = this.sections.get(day);
|
|
676
896
|
let pool = this.pools.get(day);
|
|
@@ -687,6 +907,7 @@ var ListView = class {
|
|
|
687
907
|
this.sections.set(day, section);
|
|
688
908
|
this.pools.set(day, pool);
|
|
689
909
|
}
|
|
910
|
+
section.classList.toggle("is-empty", !items.length);
|
|
690
911
|
pool.begin();
|
|
691
912
|
for (const n of items) {
|
|
692
913
|
pool.place(n.id, {
|
|
@@ -724,11 +945,62 @@ var ListView = class {
|
|
|
724
945
|
this.body.append(empty);
|
|
725
946
|
}
|
|
726
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
|
+
}
|
|
727
990
|
}
|
|
728
991
|
showSelection(_sel) {
|
|
729
992
|
}
|
|
730
|
-
hit(
|
|
731
|
-
|
|
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);
|
|
732
1004
|
}
|
|
733
1005
|
getScroll() {
|
|
734
1006
|
return { top: this.scroller.scrollTop, left: 0 };
|
|
@@ -740,6 +1012,12 @@ var ListView = class {
|
|
|
740
1012
|
return [...this.pools.values()].flatMap((p) => p.elementsFor(id));
|
|
741
1013
|
}
|
|
742
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();
|
|
743
1021
|
for (const p of this.pools.values()) p.clear();
|
|
744
1022
|
this.el.remove();
|
|
745
1023
|
}
|
|
@@ -1881,6 +2159,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
1881
2159
|
|
|
1882
2160
|
// src/dom/calendar.ts
|
|
1883
2161
|
var EXTERNAL_ID = "__tnc_external__";
|
|
2162
|
+
var MAX_AGENDA_DAYS = 366;
|
|
1884
2163
|
function sameOption(a, b, depth = 0) {
|
|
1885
2164
|
if (a === b) return true;
|
|
1886
2165
|
if (typeof a === "function" && typeof b === "function") return true;
|
|
@@ -1953,6 +2232,8 @@ var Calendar = class {
|
|
|
1953
2232
|
viewHost;
|
|
1954
2233
|
current = null;
|
|
1955
2234
|
anchor = 0;
|
|
2235
|
+
/** End of an agenda list grown past its `listDayCount`. */
|
|
2236
|
+
listEnd = null;
|
|
1956
2237
|
session = null;
|
|
1957
2238
|
selection = null;
|
|
1958
2239
|
suppressClick = false;
|
|
@@ -2145,6 +2426,10 @@ var Calendar = class {
|
|
|
2145
2426
|
if (!p) return;
|
|
2146
2427
|
const day = p.kind === "day" ? p.day : dayOf(p.ms, this.zone);
|
|
2147
2428
|
const ms = p.kind === "day" ? toInstant(p.day, 0, this.zone) : p.ms;
|
|
2429
|
+
if (this.agendaList()) {
|
|
2430
|
+
this.showListDay(day);
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2148
2433
|
const { startDay, endDay } = this.view.range;
|
|
2149
2434
|
if (day < startDay || day >= endDay) {
|
|
2150
2435
|
this.anchor = day;
|
|
@@ -2228,7 +2513,8 @@ var Calendar = class {
|
|
|
2228
2513
|
return out;
|
|
2229
2514
|
}
|
|
2230
2515
|
isEditable(n, what) {
|
|
2231
|
-
if (n.id === EXTERNAL_ID || this.opts.view === "
|
|
2516
|
+
if (n.id === EXTERNAL_ID || this.opts.view === "year") return false;
|
|
2517
|
+
if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
|
|
2232
2518
|
const it = n.item;
|
|
2233
2519
|
const base = it.editable ?? this.opts.editable;
|
|
2234
2520
|
const specific = what === "start" ? it.startEditable : it.durationEditable;
|
|
@@ -2425,7 +2711,8 @@ var Calendar = class {
|
|
|
2425
2711
|
}
|
|
2426
2712
|
case "list": {
|
|
2427
2713
|
const s = this.opts.listStart === "week" ? startOfWeek(a, this.firstDay) : a;
|
|
2428
|
-
|
|
2714
|
+
const end = s + Math.max(1, listDayCount);
|
|
2715
|
+
return { startDay: s, endDay: this.opts.listAgenda && this.listEnd !== null ? Math.max(end, this.listEnd) : end };
|
|
2429
2716
|
}
|
|
2430
2717
|
case "month": {
|
|
2431
2718
|
const s = startOfWeek(startOfMonth(a), this.firstDay);
|
|
@@ -2463,6 +2750,27 @@ var Calendar = class {
|
|
|
2463
2750
|
const duration = Math.max(1, parseDurationMinutes(this.opts.slotDuration, 30));
|
|
2464
2751
|
const snap = Math.max(1, parseDurationMinutes(this.opts.snapDuration ?? null, duration));
|
|
2465
2752
|
this.slot = { min, max, duration, snap, scroll: parseDurationMinutes(this.opts.scrollTime, 480) };
|
|
2753
|
+
if (navigated) this.listEnd = null;
|
|
2754
|
+
const rangeChanged = this.applyRange();
|
|
2755
|
+
this.conflicts = dependencyConflicts(this.store);
|
|
2756
|
+
this.root.className = "tnc";
|
|
2757
|
+
this.root.classList.add(`tnc--${this.opts.view}`, `tnc--height-${this.opts.height}`);
|
|
2758
|
+
if (!this.opts.timeGrid) this.root.classList.add("tnc--no-time-grid");
|
|
2759
|
+
this.renderToolbar();
|
|
2760
|
+
this.current?.destroy();
|
|
2761
|
+
this.current = this.createView();
|
|
2762
|
+
this.viewHost.replaceChildren(this.current.el);
|
|
2763
|
+
this.current.render(null);
|
|
2764
|
+
if (keep && this.current instanceof TimelineView) {
|
|
2765
|
+
this.current.setScroll({ top: keep.top, left: 0 });
|
|
2766
|
+
this.current.placeInstant(keep.ms, keep.vx);
|
|
2767
|
+
} else if (scroll) this.current.setScroll(scroll);
|
|
2768
|
+
else if (this.current instanceof TimeGridView) this.current.scrollToTime(this.slot.scroll);
|
|
2769
|
+
else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
2770
|
+
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
2771
|
+
}
|
|
2772
|
+
/** Sets the visible days and view info from the anchor; true when the range moved. */
|
|
2773
|
+
applyRange() {
|
|
2466
2774
|
const { startDay, endDay } = this.computeRange();
|
|
2467
2775
|
const hidden = new Set(this.opts.hiddenDays);
|
|
2468
2776
|
if (!this.opts.weekends) {
|
|
@@ -2482,23 +2790,32 @@ var Calendar = class {
|
|
|
2482
2790
|
const prevView = this.view;
|
|
2483
2791
|
this.view = { type: this.opts.view, range, anchor: this.anchor, title: "" };
|
|
2484
2792
|
this.view.title = this.computeTitle(startDay, endDay);
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
this.
|
|
2493
|
-
|
|
2494
|
-
if (
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
if (
|
|
2793
|
+
return !prevView || prevView.type !== this.view.type || prevView.range.startDay !== startDay || prevView.range.endDay !== endDay;
|
|
2794
|
+
}
|
|
2795
|
+
agendaList() {
|
|
2796
|
+
return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
|
|
2797
|
+
}
|
|
2798
|
+
extendList(endDay) {
|
|
2799
|
+
if (!this.agendaList()) return;
|
|
2800
|
+
const { startDay, endDay: current } = this.view.range;
|
|
2801
|
+
const end = Math.min(endDay, startDay + MAX_AGENDA_DAYS);
|
|
2802
|
+
if (end <= current) return;
|
|
2803
|
+
this.listEnd = end;
|
|
2804
|
+
this.applyRange();
|
|
2805
|
+
this.current?.render(null);
|
|
2806
|
+
this.opts.onRangeChange?.(this.view);
|
|
2807
|
+
}
|
|
2808
|
+
showListDay(day) {
|
|
2809
|
+
if (!this.agendaList()) return;
|
|
2810
|
+
if (day < this.view.range.startDay || day - this.view.range.startDay >= MAX_AGENDA_DAYS) {
|
|
2811
|
+
this.anchor = day;
|
|
2812
|
+
this.rebuild(true);
|
|
2813
|
+
}
|
|
2814
|
+
this.extendList(day + 2 * Math.max(7, this.opts.listDayCount));
|
|
2815
|
+
this.agendaList()?.scrollToDay(day);
|
|
2816
|
+
}
|
|
2817
|
+
dateClick(day, jsEvent) {
|
|
2818
|
+
this.clickSlot({ kind: "day", day }, jsEvent, true);
|
|
2502
2819
|
}
|
|
2503
2820
|
createView() {
|
|
2504
2821
|
switch (this.opts.view) {
|
|
@@ -2801,8 +3118,9 @@ var Calendar = class {
|
|
|
2801
3118
|
};
|
|
2802
3119
|
this.session = s;
|
|
2803
3120
|
if (touch && itemEl?.classList.contains("is-draggable")) {
|
|
2804
|
-
const
|
|
2805
|
-
|
|
3121
|
+
const x = scrollerOf(itemEl, "x");
|
|
3122
|
+
const y = scrollerOf(itemEl, "y");
|
|
3123
|
+
s.pan = { x, y, left: x?.scrollLeft ?? 0, top: y?.scrollTop ?? 0, active: false };
|
|
2806
3124
|
}
|
|
2807
3125
|
if (touch) {
|
|
2808
3126
|
s.timer = this.win.setTimeout(() => {
|
|
@@ -2843,8 +3161,8 @@ var Calendar = class {
|
|
|
2843
3161
|
if (!pan.active && s.timer !== null) this.win.clearTimeout(s.timer);
|
|
2844
3162
|
s.timer = null;
|
|
2845
3163
|
pan.active = true;
|
|
2846
|
-
pan.
|
|
2847
|
-
pan.
|
|
3164
|
+
if (pan.x) pan.x.scrollLeft = pan.left - (ev.clientX - s.x0);
|
|
3165
|
+
if (pan.y) pan.y.scrollTop = pan.top - (ev.clientY - s.y0);
|
|
2848
3166
|
} else if (dist > 8) this.endSession(null, true);
|
|
2849
3167
|
return;
|
|
2850
3168
|
}
|
|
@@ -3143,6 +3461,7 @@ var Calendar = class {
|
|
|
3143
3461
|
this.draggingId = null;
|
|
3144
3462
|
this.dragRow = null;
|
|
3145
3463
|
if (draggedId) for (const el of this.elementsFor(draggedId)) el.classList.remove("is-dragging");
|
|
3464
|
+
this.current?.dragEnded?.();
|
|
3146
3465
|
if (!s.active) return;
|
|
3147
3466
|
if (s.mode === "select") {
|
|
3148
3467
|
if (cancel) this.unselectSilently();
|
|
@@ -3236,6 +3555,12 @@ var Calendar = class {
|
|
|
3236
3555
|
ev.preventDefault();
|
|
3237
3556
|
};
|
|
3238
3557
|
onClick = (ev) => {
|
|
3558
|
+
const yearDay = ev.target.closest?.(".tnc-ym-day");
|
|
3559
|
+
if (yearDay && this.root.contains(yearDay)) {
|
|
3560
|
+
const day = parseDay(yearDay.dataset.date ?? "");
|
|
3561
|
+
if (day !== null) this.clickSlot({ kind: "day", day }, ev, true);
|
|
3562
|
+
return;
|
|
3563
|
+
}
|
|
3239
3564
|
const el = this.itemElFrom(ev.target);
|
|
3240
3565
|
if (!el || ev.target.closest(".tnc-resize, .tnc-port")) return;
|
|
3241
3566
|
const meta = this.meta.get(el);
|
|
@@ -3295,7 +3620,7 @@ var Calendar = class {
|
|
|
3295
3620
|
return;
|
|
3296
3621
|
}
|
|
3297
3622
|
const target = ev.target;
|
|
3298
|
-
if ((ev.key === "Enter" || ev.key === " ") && target.matches(".tnc-
|
|
3623
|
+
if ((ev.key === "Enter" || ev.key === " ") && target.matches(".tnc-dg-day[tabindex]")) {
|
|
3299
3624
|
const day = parseDay(target.dataset.date ?? "");
|
|
3300
3625
|
if (day !== null) {
|
|
3301
3626
|
ev.preventDefault();
|
|
@@ -3423,6 +3748,16 @@ var Calendar = class {
|
|
|
3423
3748
|
});
|
|
3424
3749
|
};
|
|
3425
3750
|
};
|
|
3751
|
+
function scrollerOf(el, axis) {
|
|
3752
|
+
const doc = el.ownerDocument;
|
|
3753
|
+
const win = doc.defaultView;
|
|
3754
|
+
for (let p = el.parentElement; p && p !== doc.body && p !== doc.documentElement; p = p.parentElement) {
|
|
3755
|
+
const overflow = win?.getComputedStyle(p)[axis === "x" ? "overflowX" : "overflowY"] ?? "";
|
|
3756
|
+
const room = axis === "x" ? p.scrollWidth > p.clientWidth : p.scrollHeight > p.clientHeight;
|
|
3757
|
+
if (room && /auto|scroll|overlay/.test(overflow)) return p;
|
|
3758
|
+
}
|
|
3759
|
+
return doc.scrollingElement;
|
|
3760
|
+
}
|
|
3426
3761
|
|
|
3427
3762
|
export {
|
|
3428
3763
|
Formatter,
|
package/dist/esm/index.js
CHANGED
package/dist/esm/react.js
CHANGED
|
@@ -44,6 +44,8 @@ export declare class Calendar<D = unknown> implements ViewContext<D> {
|
|
|
44
44
|
private readonly viewHost;
|
|
45
45
|
private current;
|
|
46
46
|
private anchor;
|
|
47
|
+
/** End of an agenda list grown past its `listDayCount`. */
|
|
48
|
+
private listEnd;
|
|
47
49
|
private session;
|
|
48
50
|
private selection;
|
|
49
51
|
private suppressClick;
|
|
@@ -147,6 +149,12 @@ export declare class Calendar<D = unknown> implements ViewContext<D> {
|
|
|
147
149
|
private computeRange;
|
|
148
150
|
private computeTitle;
|
|
149
151
|
private rebuild;
|
|
152
|
+
/** Sets the visible days and view info from the anchor; true when the range moved. */
|
|
153
|
+
private applyRange;
|
|
154
|
+
private agendaList;
|
|
155
|
+
extendList(endDay: Day): void;
|
|
156
|
+
showListDay(day: Day): void;
|
|
157
|
+
dateClick(day: Day, jsEvent: MouseEvent | KeyboardEvent): void;
|
|
150
158
|
private createView;
|
|
151
159
|
private renderToolbar;
|
|
152
160
|
private applyChange;
|
|
@@ -44,6 +44,8 @@ export declare class Calendar<D = unknown> implements ViewContext<D> {
|
|
|
44
44
|
private readonly viewHost;
|
|
45
45
|
private current;
|
|
46
46
|
private anchor;
|
|
47
|
+
/** End of an agenda list grown past its `listDayCount`. */
|
|
48
|
+
private listEnd;
|
|
47
49
|
private session;
|
|
48
50
|
private selection;
|
|
49
51
|
private suppressClick;
|
|
@@ -147,6 +149,12 @@ export declare class Calendar<D = unknown> implements ViewContext<D> {
|
|
|
147
149
|
private computeRange;
|
|
148
150
|
private computeTitle;
|
|
149
151
|
private rebuild;
|
|
152
|
+
/** Sets the visible days and view info from the anchor; true when the range moved. */
|
|
153
|
+
private applyRange;
|
|
154
|
+
private agendaList;
|
|
155
|
+
extendList(endDay: Day): void;
|
|
156
|
+
showListDay(day: Day): void;
|
|
157
|
+
dateClick(day: Day, jsEvent: MouseEvent | KeyboardEvent): void;
|
|
150
158
|
private createView;
|
|
151
159
|
private renderToolbar;
|
|
152
160
|
private applyChange;
|
|
@@ -70,6 +70,12 @@ export interface ViewContext<D = unknown> {
|
|
|
70
70
|
id: string;
|
|
71
71
|
rowId: string;
|
|
72
72
|
} | null;
|
|
73
|
+
/** Reports a day picked outside the grid (year days, the agenda strip) through `onDateClick`. */
|
|
74
|
+
dateClick(day: Day, jsEvent: MouseEvent | KeyboardEvent): void;
|
|
75
|
+
/** Agenda lists: grows the range to end at `endDay`. */
|
|
76
|
+
extendList(endDay: Day): void;
|
|
77
|
+
/** Agenda lists: brings `day` to the top, moving the range to reach it. */
|
|
78
|
+
showListDay(day: Day): void;
|
|
73
79
|
}
|
|
74
80
|
export interface View {
|
|
75
81
|
readonly el: HTMLElement;
|
|
@@ -91,6 +97,8 @@ export interface View {
|
|
|
91
97
|
scrollToTime?(minute: number): void;
|
|
92
98
|
/** Item elements currently shown for an id. */
|
|
93
99
|
elementsFor(id: string): HTMLElement[];
|
|
100
|
+
/** A drag ended, dropped or not. */
|
|
101
|
+
dragEnded?(): void;
|
|
94
102
|
destroy(): void;
|
|
95
103
|
}
|
|
96
104
|
export declare class ItemPool<D> {
|
|
@@ -70,6 +70,12 @@ export interface ViewContext<D = unknown> {
|
|
|
70
70
|
id: string;
|
|
71
71
|
rowId: string;
|
|
72
72
|
} | null;
|
|
73
|
+
/** Reports a day picked outside the grid (year days, the agenda strip) through `onDateClick`. */
|
|
74
|
+
dateClick(day: Day, jsEvent: MouseEvent | KeyboardEvent): void;
|
|
75
|
+
/** Agenda lists: grows the range to end at `endDay`. */
|
|
76
|
+
extendList(endDay: Day): void;
|
|
77
|
+
/** Agenda lists: brings `day` to the top, moving the range to reach it. */
|
|
78
|
+
showListDay(day: Day): void;
|
|
73
79
|
}
|
|
74
80
|
export interface View {
|
|
75
81
|
readonly el: HTMLElement;
|
|
@@ -91,6 +97,8 @@ export interface View {
|
|
|
91
97
|
scrollToTime?(minute: number): void;
|
|
92
98
|
/** Item elements currently shown for an id. */
|
|
93
99
|
elementsFor(id: string): HTMLElement[];
|
|
100
|
+
/** A drag ended, dropped or not. */
|
|
101
|
+
dragEnded?(): void;
|
|
94
102
|
destroy(): void;
|
|
95
103
|
}
|
|
96
104
|
export declare class ItemPool<D> {
|