@tasknotes/calendar 0.1.2 → 0.1.4
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 +23 -0
- package/README.md +6 -1
- package/dist/calendar.css +267 -0
- package/dist/cjs/index.cjs +537 -62
- package/dist/cjs/react.cjs +537 -62
- package/dist/esm/{chunk-ZJQMMA6W.js → chunk-26DEUGQH.js} +537 -62
- package/dist/esm/index.js +1 -1
- package/dist/esm/react.js +1 -1
- package/dist/types/dom/calendar.d.cts +21 -0
- package/dist/types/dom/calendar.d.ts +21 -0
- package/dist/types/dom/context.d.cts +9 -0
- package/dist/types/dom/context.d.ts +9 -0
- package/dist/types/dom/options.d.cts +50 -0
- package/dist/types/dom/options.d.ts +50 -0
- package/dist/types/dom/views/list.d.cts +25 -2
- package/dist/types/dom/views/list.d.ts +25 -2
- package/dist/types/dom/views/strip.d.cts +63 -0
- package/dist/types/dom/views/strip.d.ts +63 -0
- package/package.json +1 -1
|
@@ -164,6 +164,10 @@ function defaultOptions() {
|
|
|
164
164
|
dayCount: 3,
|
|
165
165
|
listDayCount: 7,
|
|
166
166
|
listStart: "date",
|
|
167
|
+
listAgenda: false,
|
|
168
|
+
dateStrip: false,
|
|
169
|
+
listLeadTitle: null,
|
|
170
|
+
listAddText: null,
|
|
167
171
|
monthDisplay: "full",
|
|
168
172
|
timeZone: "local",
|
|
169
173
|
locale: void 0,
|
|
@@ -213,6 +217,10 @@ var LAYOUT_KEYS = /* @__PURE__ */ new Set([
|
|
|
213
217
|
"dayCount",
|
|
214
218
|
"listDayCount",
|
|
215
219
|
"listStart",
|
|
220
|
+
"listAgenda",
|
|
221
|
+
"dateStrip",
|
|
222
|
+
"listLeadTitle",
|
|
223
|
+
"listAddText",
|
|
216
224
|
"monthDisplay",
|
|
217
225
|
"timeZone",
|
|
218
226
|
"locale",
|
|
@@ -641,21 +649,48 @@ var DayGridView = class {
|
|
|
641
649
|
};
|
|
642
650
|
|
|
643
651
|
// src/dom/views/list.ts
|
|
652
|
+
var LOAD_AHEAD = "0px 0px 600px 0px";
|
|
644
653
|
var ListView = class {
|
|
645
654
|
constructor(ctx) {
|
|
646
655
|
this.ctx = ctx;
|
|
647
|
-
|
|
648
|
-
this.
|
|
649
|
-
this.
|
|
656
|
+
const { doc, win, opts } = ctx;
|
|
657
|
+
this.el = h(doc, "div", "tnc-list");
|
|
658
|
+
this.scroller = h(doc, "div", "tnc-list-scroll");
|
|
659
|
+
this.body = h(doc, "div", "tnc-list-body", { role: "list", "aria-label": ctx.view.title });
|
|
650
660
|
this.scroller.append(this.body);
|
|
651
661
|
this.el.append(this.scroller);
|
|
662
|
+
this.agenda = opts.listAgenda;
|
|
663
|
+
if (!this.agenda) return;
|
|
664
|
+
this.el.classList.add("tnc-list--agenda");
|
|
665
|
+
this.sentinel = h(doc, "div", "tnc-list-more", { "aria-hidden": "true" });
|
|
666
|
+
this.scroller.append(this.sentinel);
|
|
667
|
+
const IO = win.IntersectionObserver;
|
|
668
|
+
if (IO) {
|
|
669
|
+
this.loader = new IO(
|
|
670
|
+
(entries) => {
|
|
671
|
+
if (entries.some((e) => e.isIntersecting)) ctx.extendList(ctx.view.range.endDay + Math.max(7, opts.listDayCount));
|
|
672
|
+
},
|
|
673
|
+
// Auto height scrolls the page, so watch the viewport; fixed height scrolls the list.
|
|
674
|
+
{ root: opts.height === "auto" ? null : this.scroller, rootMargin: LOAD_AHEAD }
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
win.addEventListener("scroll", this.onScroll, { capture: true, passive: true });
|
|
678
|
+
this.scroller.addEventListener("wheel", this.fold, { passive: true });
|
|
679
|
+
this.scroller.addEventListener("touchmove", this.fold, { passive: true });
|
|
652
680
|
}
|
|
653
681
|
ctx;
|
|
654
682
|
el;
|
|
655
683
|
scroller;
|
|
656
684
|
body;
|
|
685
|
+
agenda;
|
|
657
686
|
pools = /* @__PURE__ */ new Map();
|
|
658
687
|
sections = /* @__PURE__ */ new Map();
|
|
688
|
+
lead = null;
|
|
689
|
+
sentinel = null;
|
|
690
|
+
loader = null;
|
|
691
|
+
frame = null;
|
|
692
|
+
/** A picked day stays marked until the list is scrolled by hand. */
|
|
693
|
+
pinned = false;
|
|
659
694
|
itemsOn(day) {
|
|
660
695
|
const { store, opts } = this.ctx;
|
|
661
696
|
const out = store.spanningIn(day, day + 1, true);
|
|
@@ -664,46 +699,45 @@ var ListView = class {
|
|
|
664
699
|
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));
|
|
665
700
|
}
|
|
666
701
|
render(_change) {
|
|
667
|
-
const { ctx } = this;
|
|
668
|
-
const { doc, days } = ctx;
|
|
702
|
+
const { ctx, agenda } = this;
|
|
703
|
+
const { doc, days, opts } = ctx;
|
|
704
|
+
const includes = agenda && opts.listLeadTitle !== null ? opts.listLeadIncludes : void 0;
|
|
705
|
+
const leadItems = [];
|
|
706
|
+
const inLead = /* @__PURE__ */ new Set();
|
|
669
707
|
const keep = /* @__PURE__ */ new Set();
|
|
670
708
|
let prev = null;
|
|
671
709
|
for (const day of days) {
|
|
672
|
-
|
|
673
|
-
if (
|
|
710
|
+
let items = this.itemsOn(day);
|
|
711
|
+
if (includes) {
|
|
712
|
+
items = items.filter((n) => {
|
|
713
|
+
if (!includes(n.item)) return true;
|
|
714
|
+
if (!inLead.has(n.id)) {
|
|
715
|
+
inLead.add(n.id);
|
|
716
|
+
leadItems.push(n);
|
|
717
|
+
}
|
|
718
|
+
return false;
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
if (!items.length && !agenda) continue;
|
|
674
722
|
keep.add(day);
|
|
675
723
|
let section = this.sections.get(day);
|
|
676
724
|
let pool = this.pools.get(day);
|
|
677
725
|
if (!section || !pool) {
|
|
678
726
|
const arg = ctx.dayArg(day);
|
|
679
727
|
const heading = h(doc, "div", ["tnc-list-day", ...ctx.dayClasses(day)].join(" "), { "data-date": arg.dateStr });
|
|
680
|
-
const custom =
|
|
728
|
+
const custom = opts.dayHeaderContent?.(arg);
|
|
681
729
|
const label = custom ?? ctx.fmt.longDate(day);
|
|
682
|
-
heading.append(
|
|
683
|
-
const
|
|
684
|
-
section = h(doc, "div", "tnc-list-section", { role: "listitem", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr }, heading,
|
|
685
|
-
|
|
686
|
-
|
|
730
|
+
heading.append(opts.navLinks ? ctx.navLink(day, label, "tnc-list-day-link") : label);
|
|
731
|
+
const rows = h(doc, "div", "tnc-list-rows", { role: "group" });
|
|
732
|
+
section = h(doc, "div", "tnc-list-section", { role: "listitem", "aria-label": ctx.fmt.longDate(day), "data-date": arg.dateStr }, heading, rows);
|
|
733
|
+
if (agenda && opts.listAddText !== null) section.append(this.addRow(day));
|
|
734
|
+
opts.dayHeaderDidMount?.({ ...arg, el: heading });
|
|
735
|
+
pool = new ItemPool(ctx, rows);
|
|
687
736
|
this.sections.set(day, section);
|
|
688
737
|
this.pools.set(day, pool);
|
|
689
738
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
pool.place(n.id, {
|
|
693
|
-
n,
|
|
694
|
-
day,
|
|
695
|
-
isStart: n.startDay === day,
|
|
696
|
-
isEnd: n.endDay === day + 1,
|
|
697
|
-
variant: "list",
|
|
698
|
-
mirror: false
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
|
-
pool.end();
|
|
702
|
-
const rows = pool.container;
|
|
703
|
-
for (const n of items) {
|
|
704
|
-
const el = pool.elementsFor(n.id)[0];
|
|
705
|
-
if (el) rows.append(el);
|
|
706
|
-
}
|
|
739
|
+
section.classList.toggle("is-empty", !items.length);
|
|
740
|
+
this.fill(pool, items, () => day);
|
|
707
741
|
if (prev ? prev.nextSibling !== section : this.body.firstChild !== section) {
|
|
708
742
|
if (prev) prev.after(section);
|
|
709
743
|
else this.body.prepend(section);
|
|
@@ -717,18 +751,111 @@ var ListView = class {
|
|
|
717
751
|
section.remove();
|
|
718
752
|
this.sections.delete(day);
|
|
719
753
|
}
|
|
754
|
+
this.renderLead(leadItems);
|
|
720
755
|
let empty = this.body.querySelector(":scope > .tnc-list-empty");
|
|
721
|
-
if (!keep.size) {
|
|
756
|
+
if (!keep.size && !leadItems.length) {
|
|
722
757
|
if (!empty) {
|
|
723
758
|
empty = h(doc, "div", "tnc-list-empty", { role: "listitem" }, ctx.text.noItems);
|
|
724
759
|
this.body.append(empty);
|
|
725
760
|
}
|
|
726
761
|
} else empty?.remove();
|
|
762
|
+
this.ctx.strip?.draw();
|
|
763
|
+
if (this.loader && this.sentinel) {
|
|
764
|
+
this.loader.unobserve(this.sentinel);
|
|
765
|
+
this.loader.observe(this.sentinel);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/** Places items in a pool, keeping DOM order equal to item order. */
|
|
769
|
+
fill(pool, items, dayFor) {
|
|
770
|
+
pool.begin();
|
|
771
|
+
for (const n of items) {
|
|
772
|
+
const d = dayFor(n);
|
|
773
|
+
pool.place(n.id, { n, day: d, isStart: n.startDay === d, isEnd: n.endDay === d + 1, variant: "list", mirror: false });
|
|
774
|
+
}
|
|
775
|
+
pool.end();
|
|
776
|
+
for (const n of items) {
|
|
777
|
+
const el = pool.elementsFor(n.id)[0];
|
|
778
|
+
if (el) pool.container.append(el);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
renderLead(items) {
|
|
782
|
+
const { ctx } = this;
|
|
783
|
+
const { doc, opts } = ctx;
|
|
784
|
+
if (!items.length || opts.listLeadTitle === null) {
|
|
785
|
+
if (!this.lead) return;
|
|
786
|
+
opts.listLeadWillUnmount?.({ el: this.lead.slot });
|
|
787
|
+
this.lead.pool.clear();
|
|
788
|
+
this.lead.section.remove();
|
|
789
|
+
this.lead = null;
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
if (!this.lead) {
|
|
793
|
+
const title = opts.listLeadTitle;
|
|
794
|
+
const slot = h(doc, "div", "tnc-list-lead-slot", { "data-tnc-no-drag": "" });
|
|
795
|
+
const heading = h(doc, "div", "tnc-list-day tnc-list-lead-head", null, h(doc, "span", "tnc-list-lead-title", null, title), slot);
|
|
796
|
+
const rows = h(doc, "div", "tnc-list-rows", { role: "group" });
|
|
797
|
+
const section = h(doc, "div", "tnc-list-section tnc-list-lead", { role: "listitem", "aria-label": title }, heading, rows);
|
|
798
|
+
this.lead = { section, slot, pool: new ItemPool(ctx, rows) };
|
|
799
|
+
opts.listLeadDidMount?.({ el: slot });
|
|
800
|
+
}
|
|
801
|
+
if (this.body.firstChild !== this.lead.section) this.body.prepend(this.lead.section);
|
|
802
|
+
const first = ctx.days[0] ?? ctx.view.range.startDay;
|
|
803
|
+
this.fill(this.lead.pool, items, (n) => Math.max(n.startDay, first));
|
|
804
|
+
}
|
|
805
|
+
addRow(day) {
|
|
806
|
+
const { ctx } = this;
|
|
807
|
+
const label = ctx.opts.listAddText ?? "";
|
|
808
|
+
const btn = h(ctx.doc, "button", "tnc-list-add", { type: "button", "aria-label": `${label}, ${ctx.fmt.longDate(day)}` }, label);
|
|
809
|
+
btn.addEventListener("click", (ev) => ctx.opts.onListAdd?.({ ...ctx.dayArg(day), jsEvent: ev }));
|
|
810
|
+
return btn;
|
|
811
|
+
}
|
|
812
|
+
/** Brings a day's section to the top, under the strip; the first day brings the lead section too. */
|
|
813
|
+
scrollToDay(day) {
|
|
814
|
+
const section = day === this.ctx.days[0] && this.lead ? this.lead.section : this.sections.get(day);
|
|
815
|
+
if (!section) return;
|
|
816
|
+
this.ctx.strip?.measure();
|
|
817
|
+
section.scrollIntoView({ block: "start" });
|
|
818
|
+
this.ctx.strip?.setFocus(day);
|
|
819
|
+
this.pinned = true;
|
|
820
|
+
}
|
|
821
|
+
fold = () => {
|
|
822
|
+
this.pinned = false;
|
|
823
|
+
const strip = this.ctx.strip;
|
|
824
|
+
if (strip?.expanded) strip.setExpanded(false);
|
|
825
|
+
};
|
|
826
|
+
onScroll = () => {
|
|
827
|
+
if (this.frame !== null) return;
|
|
828
|
+
this.frame = this.ctx.win.requestAnimationFrame(() => {
|
|
829
|
+
this.frame = null;
|
|
830
|
+
this.followTop();
|
|
831
|
+
});
|
|
832
|
+
};
|
|
833
|
+
/** Marks the first day not hidden under the strip. */
|
|
834
|
+
followTop() {
|
|
835
|
+
const strip = this.ctx.strip;
|
|
836
|
+
if (!strip || this.pinned) return;
|
|
837
|
+
const edge = strip.el.getBoundingClientRect().bottom + 1;
|
|
838
|
+
for (const day of this.ctx.days) {
|
|
839
|
+
const section = this.sections.get(day);
|
|
840
|
+
if (section && section.getBoundingClientRect().bottom > edge) {
|
|
841
|
+
strip.setFocus(day);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
727
845
|
}
|
|
728
846
|
showSelection(_sel) {
|
|
729
847
|
}
|
|
730
|
-
hit(
|
|
731
|
-
|
|
848
|
+
hit(x, y) {
|
|
849
|
+
const el = this.ctx.doc.elementFromPoint(x, y);
|
|
850
|
+
const section = el?.closest(".tnc-list-section");
|
|
851
|
+
if (!section || !this.body.contains(section)) return null;
|
|
852
|
+
if (section === this.lead?.section) {
|
|
853
|
+
const item = el?.closest(".tnc-item");
|
|
854
|
+
const meta = item ? this.ctx.meta.get(item) : void 0;
|
|
855
|
+
return { kind: "day", day: meta?.day ?? this.ctx.days[0] ?? this.ctx.view.range.startDay };
|
|
856
|
+
}
|
|
857
|
+
const day = parseDay(section.dataset.date ?? "");
|
|
858
|
+
return day === null ? null : { kind: "day", day };
|
|
732
859
|
}
|
|
733
860
|
getScroll() {
|
|
734
861
|
return { top: this.scroller.scrollTop, left: 0 };
|
|
@@ -737,14 +864,231 @@ var ListView = class {
|
|
|
737
864
|
this.scroller.scrollTop = s.top;
|
|
738
865
|
}
|
|
739
866
|
elementsFor(id) {
|
|
740
|
-
|
|
867
|
+
const pools = [...this.pools.values(), ...this.lead ? [this.lead.pool] : []];
|
|
868
|
+
return pools.flatMap((p) => p.elementsFor(id));
|
|
741
869
|
}
|
|
742
870
|
destroy() {
|
|
871
|
+
const { win, opts } = this.ctx;
|
|
872
|
+
win.removeEventListener("scroll", this.onScroll, { capture: true });
|
|
873
|
+
if (this.frame !== null) win.cancelAnimationFrame(this.frame);
|
|
874
|
+
this.loader?.disconnect();
|
|
875
|
+
if (this.lead) opts.listLeadWillUnmount?.({ el: this.lead.slot });
|
|
876
|
+
this.lead?.pool.clear();
|
|
743
877
|
for (const p of this.pools.values()) p.clear();
|
|
744
878
|
this.el.remove();
|
|
745
879
|
}
|
|
746
880
|
};
|
|
747
881
|
|
|
882
|
+
// src/dom/views/strip.ts
|
|
883
|
+
var SWIPE_PX = 40;
|
|
884
|
+
var DateStrip = class {
|
|
885
|
+
constructor(ctx, pick, focus, onShown) {
|
|
886
|
+
this.ctx = ctx;
|
|
887
|
+
this.pick = pick;
|
|
888
|
+
this.onShown = onShown;
|
|
889
|
+
const { doc, text } = ctx;
|
|
890
|
+
this.focus = focus;
|
|
891
|
+
this.shown = startOfWeek(focus, ctx.firstDay);
|
|
892
|
+
this.el = h(doc, "div", "tnc-strip");
|
|
893
|
+
const head = h(doc, "div", "tnc-strip-head");
|
|
894
|
+
this.title = h(doc, "button", "tnc-strip-title", { type: "button", "aria-expanded": "false" });
|
|
895
|
+
this.title.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
896
|
+
const prev = h(doc, "button", "tnc-strip-nav tnc-strip-prev", { type: "button", "aria-label": text.prev, title: text.prev }, "\u2039");
|
|
897
|
+
const next = h(doc, "button", "tnc-strip-nav tnc-strip-next", { type: "button", "aria-label": text.next, title: text.next }, "\u203A");
|
|
898
|
+
prev.addEventListener("click", () => this.step(-1));
|
|
899
|
+
next.addEventListener("click", () => this.step(1));
|
|
900
|
+
this.todayBtn = h(doc, "button", "tnc-strip-today", { type: "button" }, text.today);
|
|
901
|
+
this.todayBtn.addEventListener("click", (ev) => this.pick(ctx.today, ev));
|
|
902
|
+
this.slot = h(doc, "div", "tnc-strip-slot", { "data-tnc-no-drag": "" });
|
|
903
|
+
head.append(this.title, this.todayBtn, prev, next, this.slot);
|
|
904
|
+
this.dows = h(doc, "div", "tnc-strip-dows", { "aria-hidden": "true" });
|
|
905
|
+
this.grid = h(doc, "div", "tnc-strip-grid", { role: "group" });
|
|
906
|
+
const handle = h(doc, "button", "tnc-strip-handle", { type: "button", tabindex: "-1", "aria-hidden": "true" });
|
|
907
|
+
handle.addEventListener("click", () => this.setExpanded(!this.expanded));
|
|
908
|
+
this.el.append(head, this.dows, this.grid, handle);
|
|
909
|
+
this.grid.addEventListener("click", this.onClick);
|
|
910
|
+
this.grid.addEventListener("keydown", this.onKeyDown);
|
|
911
|
+
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
912
|
+
this.el.addEventListener("pointerup", this.onPointerUp);
|
|
913
|
+
this.el.addEventListener("pointercancel", this.onPointerCancel);
|
|
914
|
+
this.el.addEventListener("click", this.onClickCapture, true);
|
|
915
|
+
this.draw();
|
|
916
|
+
ctx.opts.stripSlotDidMount?.({ el: this.slot });
|
|
917
|
+
}
|
|
918
|
+
ctx;
|
|
919
|
+
pick;
|
|
920
|
+
onShown;
|
|
921
|
+
el;
|
|
922
|
+
title;
|
|
923
|
+
dows;
|
|
924
|
+
grid;
|
|
925
|
+
slot;
|
|
926
|
+
todayBtn;
|
|
927
|
+
/** Day at the top of the list. */
|
|
928
|
+
focus;
|
|
929
|
+
/** First day of the week shown, or the month shown when expanded. */
|
|
930
|
+
shown;
|
|
931
|
+
target = null;
|
|
932
|
+
/** Days tinted as on screen besides the focus (a time grid's range). */
|
|
933
|
+
range = null;
|
|
934
|
+
shownKey = "";
|
|
935
|
+
swipe = null;
|
|
936
|
+
swallowClick = false;
|
|
937
|
+
expanded = false;
|
|
938
|
+
/** The list scrolled or the grid moved: mark the day and keep it in view. */
|
|
939
|
+
setFocus(day, range = this.range) {
|
|
940
|
+
const sameRange = range?.start === this.range?.start && range?.end === this.range?.end;
|
|
941
|
+
if (day === this.focus && sameRange) return;
|
|
942
|
+
this.range = range;
|
|
943
|
+
if (day !== this.focus) {
|
|
944
|
+
this.focus = day;
|
|
945
|
+
if (!this.expanded) this.shown = startOfWeek(day, this.ctx.firstDay);
|
|
946
|
+
else if (startOfMonth(day) !== this.shown) this.shown = startOfMonth(day);
|
|
947
|
+
}
|
|
948
|
+
this.draw();
|
|
949
|
+
}
|
|
950
|
+
/** Publishes the strip's height as `--tnc-strip-h`, for what sticks below it. */
|
|
951
|
+
measure() {
|
|
952
|
+
this.el.parentElement?.style.setProperty("--tnc-strip-h", `${this.el.offsetHeight}px`);
|
|
953
|
+
}
|
|
954
|
+
/** First day and end (exclusive) of the days on show. */
|
|
955
|
+
window() {
|
|
956
|
+
const days = this.days();
|
|
957
|
+
return { start: days[0], end: days[days.length - 1] + 1 };
|
|
958
|
+
}
|
|
959
|
+
setExpanded(on) {
|
|
960
|
+
if (on === this.expanded) return;
|
|
961
|
+
this.expanded = on;
|
|
962
|
+
this.shown = on ? startOfMonth(this.focus) : startOfWeek(this.focus, this.ctx.firstDay);
|
|
963
|
+
this.draw();
|
|
964
|
+
}
|
|
965
|
+
/** Highlights the day a dragged item would land on. */
|
|
966
|
+
markTarget(day) {
|
|
967
|
+
if (day === this.target) return;
|
|
968
|
+
this.target = day;
|
|
969
|
+
for (const cell of this.grid.querySelectorAll(".tnc-strip-day"))
|
|
970
|
+
cell.classList.toggle("is-drop-target", day !== null && cell.dataset.date === this.ctx.dayArg(day).dateStr);
|
|
971
|
+
}
|
|
972
|
+
dayAt(el) {
|
|
973
|
+
const cell = el.closest(".tnc-strip-day");
|
|
974
|
+
return cell && this.grid.contains(cell) ? parseDay(cell.dataset.date ?? "") : null;
|
|
975
|
+
}
|
|
976
|
+
step(dir) {
|
|
977
|
+
this.shown = this.expanded ? addMonths(this.shown, dir) : this.shown + 7 * dir;
|
|
978
|
+
this.draw();
|
|
979
|
+
}
|
|
980
|
+
days() {
|
|
981
|
+
if (!this.expanded) return Array.from({ length: 7 }, (_, i) => this.shown + i);
|
|
982
|
+
const first = startOfWeek(this.shown, this.ctx.firstDay);
|
|
983
|
+
const end = addMonths(this.shown, 1);
|
|
984
|
+
const weeks = Math.ceil((end - first) / 7);
|
|
985
|
+
return Array.from({ length: weeks * 7 }, (_, i) => first + i);
|
|
986
|
+
}
|
|
987
|
+
/** Redraws the cells; also called on every list render to refresh the dots. */
|
|
988
|
+
draw() {
|
|
989
|
+
const { ctx } = this;
|
|
990
|
+
const { doc, fmt, store } = ctx;
|
|
991
|
+
const days = this.days();
|
|
992
|
+
const titleDay = this.expanded ? this.shown : days.includes(this.focus) ? this.focus : this.shown + 3;
|
|
993
|
+
this.title.replaceChildren(fmt.monthYear(titleDay), h(doc, "span", "tnc-strip-caret", { "aria-hidden": "true" }));
|
|
994
|
+
this.title.setAttribute("aria-expanded", String(this.expanded));
|
|
995
|
+
this.el.classList.toggle("is-expanded", this.expanded);
|
|
996
|
+
this.todayBtn.hidden = this.focus === ctx.today;
|
|
997
|
+
this.dows.replaceChildren(...days.slice(0, 7).map((d) => h(doc, "span", null, null, fmt.weekdayNarrow(d))));
|
|
998
|
+
const focused = this.grid.contains(doc.activeElement) ? doc.activeElement.dataset.date : void 0;
|
|
999
|
+
const month = ymdOfDay(this.shown).month;
|
|
1000
|
+
const tabDay = days.includes(this.focus) ? this.focus : days[0];
|
|
1001
|
+
const cells = days.map((d) => {
|
|
1002
|
+
const arg = ctx.dayArg(d);
|
|
1003
|
+
const count = store.inRange(d, d + 1).filter((n) => n.item.display !== "background" && n.item.display !== "none").length;
|
|
1004
|
+
const first = ymdOfDay(d).day === 1 && d !== days[0] && !this.expanded;
|
|
1005
|
+
const label = fmt.longDate(d);
|
|
1006
|
+
const cell = h(
|
|
1007
|
+
doc,
|
|
1008
|
+
"button",
|
|
1009
|
+
[
|
|
1010
|
+
"tnc-strip-day",
|
|
1011
|
+
...ctx.dayClasses(d),
|
|
1012
|
+
d === this.focus ? "is-focus" : "",
|
|
1013
|
+
d !== this.focus && this.range && d >= this.range.start && d < this.range.end ? "is-in-range" : "",
|
|
1014
|
+
count ? "has-items" : "",
|
|
1015
|
+
this.expanded && ymdOfDay(d).month !== month ? "is-other-month" : "",
|
|
1016
|
+
d === this.target ? "is-drop-target" : ""
|
|
1017
|
+
].filter(Boolean).join(" "),
|
|
1018
|
+
{
|
|
1019
|
+
type: "button",
|
|
1020
|
+
"data-date": arg.dateStr,
|
|
1021
|
+
"aria-label": count ? `${label}, ${ctx.text.itemCount(count)}` : label,
|
|
1022
|
+
"aria-pressed": String(d === this.focus),
|
|
1023
|
+
tabindex: d === tabDay ? "0" : "-1"
|
|
1024
|
+
}
|
|
1025
|
+
);
|
|
1026
|
+
if (first) cell.append(h(doc, "span", "tnc-strip-month", { "aria-hidden": "true" }, fmt.monthShort(d)));
|
|
1027
|
+
cell.append(h(doc, "span", "tnc-strip-num", null, fmt.dayNumber(d)), h(doc, "span", "tnc-strip-dot", { "aria-hidden": "true" }));
|
|
1028
|
+
return cell;
|
|
1029
|
+
});
|
|
1030
|
+
this.grid.replaceChildren(...cells);
|
|
1031
|
+
this.grid.setAttribute("aria-label", fmt.monthYear(titleDay));
|
|
1032
|
+
if (focused) this.grid.querySelector(`[data-date="${focused}"]`)?.focus({ preventScroll: true });
|
|
1033
|
+
const key = `${days[0]}:${days.length}`;
|
|
1034
|
+
if (key !== this.shownKey) {
|
|
1035
|
+
this.shownKey = key;
|
|
1036
|
+
this.onShown();
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
onClick = (ev) => {
|
|
1040
|
+
const day = this.dayAt(ev.target);
|
|
1041
|
+
if (day === null) return;
|
|
1042
|
+
if (this.expanded) this.setExpanded(false);
|
|
1043
|
+
this.pick(day, ev);
|
|
1044
|
+
};
|
|
1045
|
+
onKeyDown = (ev) => {
|
|
1046
|
+
const day = this.dayAt(ev.target);
|
|
1047
|
+
if (day === null) return;
|
|
1048
|
+
const move = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[ev.key];
|
|
1049
|
+
if (move === void 0) return;
|
|
1050
|
+
ev.preventDefault();
|
|
1051
|
+
const next = day + move;
|
|
1052
|
+
const days = this.days();
|
|
1053
|
+
if (next < days[0] || next > days[days.length - 1]) {
|
|
1054
|
+
this.shown = this.expanded ? startOfMonth(next) : startOfWeek(next, this.ctx.firstDay);
|
|
1055
|
+
this.draw();
|
|
1056
|
+
}
|
|
1057
|
+
const cell = this.grid.querySelector(`[data-date="${this.ctx.dayArg(next).dateStr}"]`);
|
|
1058
|
+
if (!cell) return;
|
|
1059
|
+
for (const c of this.grid.querySelectorAll("[tabindex='0']")) c.setAttribute("tabindex", "-1");
|
|
1060
|
+
cell.setAttribute("tabindex", "0");
|
|
1061
|
+
cell.focus();
|
|
1062
|
+
};
|
|
1063
|
+
onPointerDown = (ev) => {
|
|
1064
|
+
if (ev.button !== 0 || this.ctx.draggingId) return;
|
|
1065
|
+
this.swipe = { id: ev.pointerId, x: ev.clientX, y: ev.clientY };
|
|
1066
|
+
};
|
|
1067
|
+
onPointerUp = (ev) => {
|
|
1068
|
+
const s = this.swipe;
|
|
1069
|
+
this.swipe = null;
|
|
1070
|
+
if (!s || s.id !== ev.pointerId) return;
|
|
1071
|
+
const dx = ev.clientX - s.x;
|
|
1072
|
+
if (Math.abs(dx) < SWIPE_PX || Math.abs(dx) < Math.abs(ev.clientY - s.y)) return;
|
|
1073
|
+
this.swallowClick = true;
|
|
1074
|
+
this.step(dx < 0 ? 1 : -1);
|
|
1075
|
+
};
|
|
1076
|
+
onPointerCancel = () => {
|
|
1077
|
+
this.swipe = null;
|
|
1078
|
+
};
|
|
1079
|
+
/** A swipe that ends on a day is not a pick. */
|
|
1080
|
+
onClickCapture = (ev) => {
|
|
1081
|
+
if (!this.swallowClick) return;
|
|
1082
|
+
this.swallowClick = false;
|
|
1083
|
+
ev.stopPropagation();
|
|
1084
|
+
ev.preventDefault();
|
|
1085
|
+
};
|
|
1086
|
+
destroy() {
|
|
1087
|
+
this.ctx.opts.stripSlotWillUnmount?.({ el: this.slot });
|
|
1088
|
+
this.el.remove();
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
|
|
748
1092
|
// src/dom/views/timegrid.ts
|
|
749
1093
|
var BAR_GAP2 = 2;
|
|
750
1094
|
var TimeGridView = class {
|
|
@@ -1881,6 +2225,7 @@ ${this.ctx.text.more(items.length - 8)}` : ""}` : label;
|
|
|
1881
2225
|
|
|
1882
2226
|
// src/dom/calendar.ts
|
|
1883
2227
|
var EXTERNAL_ID = "__tnc_external__";
|
|
2228
|
+
var MAX_AGENDA_DAYS = 366;
|
|
1884
2229
|
function sameOption(a, b, depth = 0) {
|
|
1885
2230
|
if (a === b) return true;
|
|
1886
2231
|
if (typeof a === "function" && typeof b === "function") return true;
|
|
@@ -1949,10 +2294,15 @@ var Calendar = class {
|
|
|
1949
2294
|
slot = { min: 0, max: MINUTES_PER_DAY, duration: 30, snap: 30, scroll: 480 };
|
|
1950
2295
|
draggingId = null;
|
|
1951
2296
|
dragRow = null;
|
|
2297
|
+
strip = null;
|
|
2298
|
+
stripSize = null;
|
|
2299
|
+
rebuilding = false;
|
|
1952
2300
|
toolbarEl;
|
|
1953
2301
|
viewHost;
|
|
1954
2302
|
current = null;
|
|
1955
2303
|
anchor = 0;
|
|
2304
|
+
/** End of an agenda list grown past its `listDayCount`. */
|
|
2305
|
+
listEnd = null;
|
|
1956
2306
|
session = null;
|
|
1957
2307
|
selection = null;
|
|
1958
2308
|
suppressClick = false;
|
|
@@ -2145,6 +2495,10 @@ var Calendar = class {
|
|
|
2145
2495
|
if (!p) return;
|
|
2146
2496
|
const day = p.kind === "day" ? p.day : dayOf(p.ms, this.zone);
|
|
2147
2497
|
const ms = p.kind === "day" ? toInstant(p.day, 0, this.zone) : p.ms;
|
|
2498
|
+
if (this.agendaList()) {
|
|
2499
|
+
this.showListDay(day);
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2148
2502
|
const { startDay, endDay } = this.view.range;
|
|
2149
2503
|
if (day < startDay || day >= endDay) {
|
|
2150
2504
|
this.anchor = day;
|
|
@@ -2156,7 +2510,7 @@ var Calendar = class {
|
|
|
2156
2510
|
}
|
|
2157
2511
|
/** What is under a client point: a time slot, a day, or a timeline row and date. Null outside the view. */
|
|
2158
2512
|
hitTest(clientX, clientY) {
|
|
2159
|
-
return this.
|
|
2513
|
+
return this.hitAt(clientX, clientY);
|
|
2160
2514
|
}
|
|
2161
2515
|
/** Item elements currently rendered for an id. */
|
|
2162
2516
|
elementsFor(id) {
|
|
@@ -2198,6 +2552,9 @@ var Calendar = class {
|
|
|
2198
2552
|
this.unbindEvents();
|
|
2199
2553
|
this.current?.destroy();
|
|
2200
2554
|
this.current = null;
|
|
2555
|
+
this.stripSize?.disconnect();
|
|
2556
|
+
this.strip?.destroy();
|
|
2557
|
+
this.strip = null;
|
|
2201
2558
|
this.root.remove();
|
|
2202
2559
|
}
|
|
2203
2560
|
/* ========================================================== view context */
|
|
@@ -2228,7 +2585,8 @@ var Calendar = class {
|
|
|
2228
2585
|
return out;
|
|
2229
2586
|
}
|
|
2230
2587
|
isEditable(n, what) {
|
|
2231
|
-
if (n.id === EXTERNAL_ID || this.opts.view === "
|
|
2588
|
+
if (n.id === EXTERNAL_ID || this.opts.view === "year") return false;
|
|
2589
|
+
if (this.opts.view === "list" && (!this.opts.listAgenda || what === "duration")) return false;
|
|
2232
2590
|
const it = n.item;
|
|
2233
2591
|
const base = it.editable ?? this.opts.editable;
|
|
2234
2592
|
const specific = what === "start" ? it.startEditable : it.durationEditable;
|
|
@@ -2425,7 +2783,8 @@ var Calendar = class {
|
|
|
2425
2783
|
}
|
|
2426
2784
|
case "list": {
|
|
2427
2785
|
const s = this.opts.listStart === "week" ? startOfWeek(a, this.firstDay) : a;
|
|
2428
|
-
|
|
2786
|
+
const end = s + Math.max(1, listDayCount);
|
|
2787
|
+
return { startDay: s, endDay: this.opts.listAgenda && this.listEnd !== null ? Math.max(end, this.listEnd) : end };
|
|
2429
2788
|
}
|
|
2430
2789
|
case "month": {
|
|
2431
2790
|
const s = startOfWeek(startOfMonth(a), this.firstDay);
|
|
@@ -2463,6 +2822,33 @@ var Calendar = class {
|
|
|
2463
2822
|
const duration = Math.max(1, parseDurationMinutes(this.opts.slotDuration, 30));
|
|
2464
2823
|
const snap = Math.max(1, parseDurationMinutes(this.opts.snapDuration ?? null, duration));
|
|
2465
2824
|
this.slot = { min, max, duration, snap, scroll: parseDurationMinutes(this.opts.scrollTime, 480) };
|
|
2825
|
+
if (navigated) this.listEnd = null;
|
|
2826
|
+
const prev = this.view;
|
|
2827
|
+
this.applyRange();
|
|
2828
|
+
this.conflicts = dependencyConflicts(this.store);
|
|
2829
|
+
this.root.className = "tnc";
|
|
2830
|
+
this.root.classList.add(`tnc--${this.opts.view}`, `tnc--height-${this.opts.height}`);
|
|
2831
|
+
if (!this.opts.timeGrid) this.root.classList.add("tnc--no-time-grid");
|
|
2832
|
+
this.renderToolbar();
|
|
2833
|
+
this.rebuilding = true;
|
|
2834
|
+
this.syncStrip(navigated);
|
|
2835
|
+
this.rebuilding = false;
|
|
2836
|
+
this.view.dataRange = this.dataRangeFor(this.view.range);
|
|
2837
|
+
const rangeChanged = rangeMoved(prev, this.view);
|
|
2838
|
+
this.current?.destroy();
|
|
2839
|
+
this.current = this.createView();
|
|
2840
|
+
this.viewHost.replaceChildren(this.current.el);
|
|
2841
|
+
this.current.render(null);
|
|
2842
|
+
if (keep && this.current instanceof TimelineView) {
|
|
2843
|
+
this.current.setScroll({ top: keep.top, left: 0 });
|
|
2844
|
+
this.current.placeInstant(keep.ms, keep.vx);
|
|
2845
|
+
} else if (scroll) this.current.setScroll(scroll);
|
|
2846
|
+
else if (this.current instanceof TimeGridView) this.current.scrollToTime(this.slot.scroll);
|
|
2847
|
+
else if (this.current instanceof TimelineView) this.current.scrollToDay(this.anchor);
|
|
2848
|
+
if (rangeChanged) this.opts.onRangeChange?.(this.view);
|
|
2849
|
+
}
|
|
2850
|
+
/** Sets the visible days and view info from the anchor. */
|
|
2851
|
+
applyRange() {
|
|
2466
2852
|
const { startDay, endDay } = this.computeRange();
|
|
2467
2853
|
const hidden = new Set(this.opts.hiddenDays);
|
|
2468
2854
|
if (!this.opts.weekends) {
|
|
@@ -2479,26 +2865,100 @@ var Calendar = class {
|
|
|
2479
2865
|
start: new Date(toInstant(startDay, 0, this.zone)),
|
|
2480
2866
|
end: new Date(toInstant(endDay, 0, this.zone))
|
|
2481
2867
|
};
|
|
2482
|
-
|
|
2483
|
-
this.view = { type: this.opts.view, range, anchor: this.anchor, title: "" };
|
|
2868
|
+
this.view = { type: this.opts.view, range, anchor: this.anchor, title: "", dataRange: this.dataRangeFor(range) };
|
|
2484
2869
|
this.view.title = this.computeTitle(startDay, endDay);
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
this.
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2870
|
+
}
|
|
2871
|
+
/** The range, run on to the end of the strip's days so their dots are right. */
|
|
2872
|
+
dataRangeFor(range) {
|
|
2873
|
+
const end = this.strip ? Math.max(range.endDay, this.strip.window().end) : range.endDay;
|
|
2874
|
+
if (end === range.endDay) return range;
|
|
2875
|
+
return { ...range, endDay: end, end: new Date(toInstant(end, 0, this.zone)) };
|
|
2876
|
+
}
|
|
2877
|
+
/** Adds, updates or removes the date strip for the current view. */
|
|
2878
|
+
syncStrip(navigated) {
|
|
2879
|
+
const { view, listAgenda, dateStrip } = this.opts;
|
|
2880
|
+
const list = view === "list";
|
|
2881
|
+
const want = list ? listAgenda : dateStrip && (view === "day" || view === "days" || view === "week");
|
|
2882
|
+
if (!want) {
|
|
2883
|
+
if (!this.strip) return;
|
|
2884
|
+
this.stripSize?.disconnect();
|
|
2885
|
+
this.stripSize = null;
|
|
2886
|
+
this.strip.destroy();
|
|
2887
|
+
this.strip = null;
|
|
2888
|
+
this.root.style.removeProperty("--tnc-strip-h");
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
this.root.classList.add("tnc--has-strip");
|
|
2892
|
+
const { startDay, endDay } = this.view.range;
|
|
2893
|
+
if (!this.strip) {
|
|
2894
|
+
const strip = new DateStrip(this, (day, ev) => this.pickStripDay(day, ev), startDay, () => this.stripShown());
|
|
2895
|
+
this.strip = strip;
|
|
2896
|
+
this.root.insertBefore(strip.el, this.viewHost);
|
|
2897
|
+
const RO = this.win.ResizeObserver;
|
|
2898
|
+
if (RO) {
|
|
2899
|
+
this.stripSize = new RO(() => strip.measure());
|
|
2900
|
+
this.stripSize.observe(strip.el);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
if (!list) this.strip.setFocus(startDay, { start: startDay, end: endDay });
|
|
2904
|
+
else if (navigated) this.strip.setFocus(startDay, null);
|
|
2905
|
+
this.strip.draw();
|
|
2906
|
+
}
|
|
2907
|
+
pickStripDay(day, ev) {
|
|
2908
|
+
this.dateClick(day, ev);
|
|
2909
|
+
if (this.agendaList()) {
|
|
2910
|
+
this.showListDay(day);
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
const { startDay, endDay } = this.view.range;
|
|
2914
|
+
if (this.opts.view === "week" && day >= startDay && day < endDay) return;
|
|
2915
|
+
this.anchor = day;
|
|
2916
|
+
this.rebuild(true);
|
|
2917
|
+
}
|
|
2918
|
+
/** The strip moved to other days: load theirs too. */
|
|
2919
|
+
stripShown() {
|
|
2920
|
+
if (!this.strip || this.rebuilding || !this.view) return;
|
|
2921
|
+
const next = this.dataRangeFor(this.view.range);
|
|
2922
|
+
if (next.endDay === this.view.dataRange.endDay) return;
|
|
2923
|
+
this.view = { ...this.view, dataRange: next };
|
|
2924
|
+
this.opts.onRangeChange?.(this.view);
|
|
2925
|
+
}
|
|
2926
|
+
/** What is under a point, the date strip included. */
|
|
2927
|
+
hitAt(x, y) {
|
|
2928
|
+
const strip = this.strip;
|
|
2929
|
+
if (strip) {
|
|
2930
|
+
const el = this.doc.elementFromPoint(x, y);
|
|
2931
|
+
const day = el ? strip.dayAt(el) : null;
|
|
2932
|
+
strip.markTarget(day !== null && this.draggingId ? day : null);
|
|
2933
|
+
if (day !== null) return { kind: "day", day };
|
|
2934
|
+
}
|
|
2935
|
+
return this.current?.hit(x, y) ?? null;
|
|
2936
|
+
}
|
|
2937
|
+
agendaList() {
|
|
2938
|
+
return this.opts.view === "list" && this.opts.listAgenda && this.current instanceof ListView ? this.current : null;
|
|
2939
|
+
}
|
|
2940
|
+
extendList(endDay) {
|
|
2941
|
+
if (!this.agendaList()) return;
|
|
2942
|
+
const { startDay, endDay: current } = this.view.range;
|
|
2943
|
+
const end = Math.min(endDay, startDay + MAX_AGENDA_DAYS);
|
|
2944
|
+
if (end <= current) return;
|
|
2945
|
+
this.listEnd = end;
|
|
2946
|
+
this.applyRange();
|
|
2947
|
+
this.current?.render(null);
|
|
2948
|
+
this.strip?.draw();
|
|
2949
|
+
this.opts.onRangeChange?.(this.view);
|
|
2950
|
+
}
|
|
2951
|
+
showListDay(day) {
|
|
2952
|
+
if (!this.agendaList()) return;
|
|
2953
|
+
if (day < this.view.range.startDay || day - this.view.range.startDay >= MAX_AGENDA_DAYS) {
|
|
2954
|
+
this.anchor = day;
|
|
2955
|
+
this.rebuild(true);
|
|
2956
|
+
}
|
|
2957
|
+
this.extendList(day + 2 * Math.max(7, this.opts.listDayCount));
|
|
2958
|
+
this.agendaList()?.scrollToDay(day);
|
|
2959
|
+
}
|
|
2960
|
+
dateClick(day, jsEvent) {
|
|
2961
|
+
this.clickSlot({ kind: "day", day }, jsEvent, true);
|
|
2502
2962
|
}
|
|
2503
2963
|
createView() {
|
|
2504
2964
|
switch (this.opts.view) {
|
|
@@ -2801,8 +3261,9 @@ var Calendar = class {
|
|
|
2801
3261
|
};
|
|
2802
3262
|
this.session = s;
|
|
2803
3263
|
if (touch && itemEl?.classList.contains("is-draggable")) {
|
|
2804
|
-
const
|
|
2805
|
-
|
|
3264
|
+
const x = scrollerOf(itemEl, "x");
|
|
3265
|
+
const y = scrollerOf(itemEl, "y");
|
|
3266
|
+
s.pan = { x, y, left: x?.scrollLeft ?? 0, top: y?.scrollTop ?? 0, active: false };
|
|
2806
3267
|
}
|
|
2807
3268
|
if (touch) {
|
|
2808
3269
|
s.timer = this.win.setTimeout(() => {
|
|
@@ -2843,8 +3304,8 @@ var Calendar = class {
|
|
|
2843
3304
|
if (!pan.active && s.timer !== null) this.win.clearTimeout(s.timer);
|
|
2844
3305
|
s.timer = null;
|
|
2845
3306
|
pan.active = true;
|
|
2846
|
-
pan.
|
|
2847
|
-
pan.
|
|
3307
|
+
if (pan.x) pan.x.scrollLeft = pan.left - (ev.clientX - s.x0);
|
|
3308
|
+
if (pan.y) pan.y.scrollTop = pan.top - (ev.clientY - s.y0);
|
|
2848
3309
|
} else if (dist > 8) this.endSession(null, true);
|
|
2849
3310
|
return;
|
|
2850
3311
|
}
|
|
@@ -2882,7 +3343,7 @@ var Calendar = class {
|
|
|
2882
3343
|
};
|
|
2883
3344
|
/** Proposal for the current pointer position. */
|
|
2884
3345
|
track(s, ev) {
|
|
2885
|
-
const hit = this.
|
|
3346
|
+
const hit = this.hitAt(ev.clientX, ev.clientY);
|
|
2886
3347
|
if (s.mode === "external") {
|
|
2887
3348
|
this.trackExternal(s, hit);
|
|
2888
3349
|
return;
|
|
@@ -3008,7 +3469,7 @@ var Calendar = class {
|
|
|
3008
3469
|
return this.dayProposal(hit.day, hit.day + this.opts.defaultAllDayDays);
|
|
3009
3470
|
}
|
|
3010
3471
|
if (hit.kind === "day") {
|
|
3011
|
-
const dd = hit.day - (grab.kind === "
|
|
3472
|
+
const dd = hit.day - (grab.kind === "timeline" ? hit.day : grab.day);
|
|
3012
3473
|
if (o.allDay) return this.dayProposal(o.startDay + dd, o.startDay + dd + days);
|
|
3013
3474
|
const startMs2 = this.civilToMs(this.civil(o.startMs) + dd * MINUTES_PER_DAY);
|
|
3014
3475
|
const endMs = this.civilToMs(this.civil(o.endMs) + dd * MINUTES_PER_DAY);
|
|
@@ -3143,6 +3604,7 @@ var Calendar = class {
|
|
|
3143
3604
|
this.draggingId = null;
|
|
3144
3605
|
this.dragRow = null;
|
|
3145
3606
|
if (draggedId) for (const el of this.elementsFor(draggedId)) el.classList.remove("is-dragging");
|
|
3607
|
+
this.strip?.markTarget(null);
|
|
3146
3608
|
if (!s.active) return;
|
|
3147
3609
|
if (s.mode === "select") {
|
|
3148
3610
|
if (cancel) this.unselectSilently();
|
|
@@ -3429,6 +3891,19 @@ var Calendar = class {
|
|
|
3429
3891
|
});
|
|
3430
3892
|
};
|
|
3431
3893
|
};
|
|
3894
|
+
function scrollerOf(el, axis) {
|
|
3895
|
+
const doc = el.ownerDocument;
|
|
3896
|
+
const win = doc.defaultView;
|
|
3897
|
+
for (let p = el.parentElement; p && p !== doc.body && p !== doc.documentElement; p = p.parentElement) {
|
|
3898
|
+
const overflow = win?.getComputedStyle(p)[axis === "x" ? "overflowX" : "overflowY"] ?? "";
|
|
3899
|
+
const room = axis === "x" ? p.scrollWidth > p.clientWidth : p.scrollHeight > p.clientHeight;
|
|
3900
|
+
if (room && /auto|scroll|overlay/.test(overflow)) return p;
|
|
3901
|
+
}
|
|
3902
|
+
return doc.scrollingElement;
|
|
3903
|
+
}
|
|
3904
|
+
function rangeMoved(prev, next) {
|
|
3905
|
+
return !prev || prev.type !== next.type || prev.range.startDay !== next.range.startDay || prev.range.endDay !== next.range.endDay || prev.dataRange.endDay !== next.dataRange.endDay;
|
|
3906
|
+
}
|
|
3432
3907
|
|
|
3433
3908
|
export {
|
|
3434
3909
|
Formatter,
|