@k2b/ui 0.22.0 → 0.24.0

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.
Files changed (117) hide show
  1. package/dist/browser/content/Calendar.js +1122 -1065
  2. package/dist/browser/content/Calendar.js.map +1 -1
  3. package/dist/browser/content/CalendarDayView.js +1187 -0
  4. package/dist/browser/content/CalendarDayView.js.map +1 -0
  5. package/dist/browser/content/CalendarWeekView.js +1593 -0
  6. package/dist/browser/content/CalendarWeekView.js.map +1 -0
  7. package/dist/browser/content/ChartExplorer.js +174 -82
  8. package/dist/browser/content/ChartExplorer.js.map +1 -1
  9. package/dist/browser/content/ChartFilterControls.js +90 -33
  10. package/dist/browser/content/ChartFilterControls.js.map +1 -1
  11. package/dist/browser/content/DataTable.js +235 -197
  12. package/dist/browser/content/DataTable.js.map +1 -1
  13. package/dist/browser/content/calendar-day-scroll.js +111 -0
  14. package/dist/browser/content/calendar-day-scroll.js.map +1 -0
  15. package/dist/browser/content/calendar-days.js +408 -0
  16. package/dist/browser/content/calendar-days.js.map +1 -0
  17. package/dist/browser/content/calendar-dialogs.js +236 -0
  18. package/dist/browser/content/calendar-dialogs.js.map +1 -0
  19. package/dist/browser/content/calendar-messages.js +210 -2
  20. package/dist/browser/content/calendar-messages.js.map +1 -1
  21. package/dist/browser/content/calendar-pointer.js +15 -3
  22. package/dist/browser/content/calendar-pointer.js.map +1 -1
  23. package/dist/browser/content/calendar-shared.js +328 -0
  24. package/dist/browser/content/calendar-shared.js.map +1 -0
  25. package/dist/browser/content/calendar-time-view.js +1654 -0
  26. package/dist/browser/content/calendar-time-view.js.map +1 -0
  27. package/dist/browser/content/chart-messages.js +10 -0
  28. package/dist/browser/content/chart-messages.js.map +1 -1
  29. package/dist/browser/content/index.js.map +1 -1
  30. package/dist/browser/inputs/Combobox.js +0 -1
  31. package/dist/browser/inputs/Combobox.js.map +1 -1
  32. package/dist/browser/layout/AppWorkspace.js +125 -97
  33. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  34. package/dist/browser/layout/Navigation.js +18 -3
  35. package/dist/browser/layout/Navigation.js.map +1 -1
  36. package/dist/browser/layout/PageTitle.js +18 -1
  37. package/dist/browser/layout/PageTitle.js.map +1 -1
  38. package/dist/browser/layout/SidebarSwitcher.js +505 -0
  39. package/dist/browser/layout/SidebarSwitcher.js.map +1 -0
  40. package/dist/browser/layout/app-workspace-controller.js +174 -86
  41. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  42. package/dist/browser/layout/index.js +1 -0
  43. package/dist/browser/layout/index.js.map +1 -1
  44. package/dist/browser/layout/navigation-model.js +1 -0
  45. package/dist/browser/layout/navigation-model.js.map +1 -1
  46. package/dist/browser/surfaces/StatCell.js +18 -6
  47. package/dist/browser/surfaces/StatCell.js.map +1 -1
  48. package/dist/ssr/content/Calendar.js +553 -341
  49. package/dist/ssr/content/Calendar.js.map +1 -1
  50. package/dist/ssr/content/CalendarDayView.js +800 -0
  51. package/dist/ssr/content/CalendarDayView.js.map +1 -0
  52. package/dist/ssr/content/CalendarWeekView.js +1090 -0
  53. package/dist/ssr/content/CalendarWeekView.js.map +1 -0
  54. package/dist/ssr/content/ChartExplorer.js +93 -34
  55. package/dist/ssr/content/ChartExplorer.js.map +1 -1
  56. package/dist/ssr/content/ChartFilterControls.js +49 -7
  57. package/dist/ssr/content/ChartFilterControls.js.map +1 -1
  58. package/dist/ssr/content/DataTable.js +88 -66
  59. package/dist/ssr/content/DataTable.js.map +1 -1
  60. package/dist/ssr/content/calendar-day-scroll.js +111 -0
  61. package/dist/ssr/content/calendar-day-scroll.js.map +1 -0
  62. package/dist/ssr/content/calendar-days.js +408 -0
  63. package/dist/ssr/content/calendar-days.js.map +1 -0
  64. package/dist/ssr/content/calendar-dialogs.js +159 -0
  65. package/dist/ssr/content/calendar-dialogs.js.map +1 -0
  66. package/dist/ssr/content/calendar-messages.js +210 -2
  67. package/dist/ssr/content/calendar-messages.js.map +1 -1
  68. package/dist/ssr/content/calendar-pointer.js +15 -3
  69. package/dist/ssr/content/calendar-pointer.js.map +1 -1
  70. package/dist/ssr/content/calendar-shared.js +261 -0
  71. package/dist/ssr/content/calendar-shared.js.map +1 -0
  72. package/dist/ssr/content/calendar-time-view.js +1311 -0
  73. package/dist/ssr/content/calendar-time-view.js.map +1 -0
  74. package/dist/ssr/content/chart-messages.js +10 -0
  75. package/dist/ssr/content/chart-messages.js.map +1 -1
  76. package/dist/ssr/content/index.js.map +1 -1
  77. package/dist/ssr/inputs/Combobox.js.map +1 -1
  78. package/dist/ssr/layout/AppWorkspace.js +40 -29
  79. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  80. package/dist/ssr/layout/Navigation.js +12 -2
  81. package/dist/ssr/layout/Navigation.js.map +1 -1
  82. package/dist/ssr/layout/PageTitle.js +18 -1
  83. package/dist/ssr/layout/PageTitle.js.map +1 -1
  84. package/dist/ssr/layout/SidebarSwitcher.js +271 -0
  85. package/dist/ssr/layout/SidebarSwitcher.js.map +1 -0
  86. package/dist/ssr/layout/app-workspace-controller.js +174 -86
  87. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  88. package/dist/ssr/layout/index.js +1 -0
  89. package/dist/ssr/layout/index.js.map +1 -1
  90. package/dist/ssr/layout/navigation-model.js +1 -0
  91. package/dist/ssr/layout/navigation-model.js.map +1 -1
  92. package/dist/ssr/surfaces/StatCell.js +2 -2
  93. package/dist/ssr/surfaces/StatCell.js.map +1 -1
  94. package/dist/styles.css +1 -1
  95. package/dist/types/content/Calendar.d.ts +95 -1
  96. package/dist/types/content/CalendarDayView.d.ts +7 -0
  97. package/dist/types/content/CalendarWeekView.d.ts +7 -0
  98. package/dist/types/content/ChartExplorer.d.ts +36 -1
  99. package/dist/types/content/ChartFilterControls.d.ts +6 -1
  100. package/dist/types/content/DataTable.d.ts +14 -0
  101. package/dist/types/content/calendar-day-scroll.d.ts +47 -0
  102. package/dist/types/content/calendar-days.d.ts +192 -0
  103. package/dist/types/content/calendar-dialogs.d.ts +22 -0
  104. package/dist/types/content/calendar-messages.d.ts +99 -0
  105. package/dist/types/content/calendar-pointer.d.ts +9 -0
  106. package/dist/types/content/calendar-shared.d.ts +69 -0
  107. package/dist/types/content/calendar-time-view.d.ts +252 -0
  108. package/dist/types/content/chart-messages.d.ts +5 -0
  109. package/dist/types/content/index.d.ts +2 -2
  110. package/dist/types/layout/AppWorkspace.d.ts +27 -3
  111. package/dist/types/layout/PageTitle.d.ts +2 -1
  112. package/dist/types/layout/SidebarSwitcher.d.ts +65 -0
  113. package/dist/types/layout/app-workspace-controller.d.ts +9 -1
  114. package/dist/types/layout/index.d.ts +1 -0
  115. package/dist/types/layout/navigation-model.d.ts +4 -0
  116. package/dist/types/surfaces/StatCell.d.ts +5 -0
  117. package/package.json +3 -3
@@ -0,0 +1,1090 @@
1
+ import { memo as _$memo } from "solid-js/web";
2
+ import { ssr as _$ssr } from "solid-js/web";
3
+ import { createComponent as _$createComponent } from "solid-js/web";
4
+ import { ssrStyleProperty as _$ssrStyleProperty } from "solid-js/web";
5
+ import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
6
+ import { escape as _$escape } from "solid-js/web";
7
+ var _tmpl$ = ["<span class=\"k2b-calendar-week__now-time\" style=\"", "\" aria-hidden=\"true\">", "</span>"],
8
+ _tmpl$2 = ["<span class=\"k2b-calendar-night\" style=\"", "\"></span>"],
9
+ _tmpl$3 = ["<span class=\"k2b-calendar-week__now-line\" style=\"", "\"></span>"],
10
+ _tmpl$4 = ["<div class=\"k2b-calendar-week\" data-view=\"week\"", "><div class=\"k2b-calendar-week__scroll k2b-detail-trail\" role=\"group\"", " style=\"", "\"><div class=\"k2b-calendar-week__grid\"><div class=\"k2b-calendar-week__top\"><div class=\"k2b-calendar-week__corner\" aria-hidden=\"true\">", "</div>", "<div class=\"k2b-calendar-week__all-day-label\" aria-hidden=\"true\"><span class=\"k2b-calendar-week__all-day-long\">", "</span><span class=\"k2b-calendar-week__all-day-short\">", "</span></div><div class=\"k2b-calendar-week__all-day\" role=\"group\"", " style=\"", "\">", "", "</div></div><div class=\"k2b-calendar-week__body\" style=\"", "\"><div class=\"k2b-calendar-week__gutter\">", "", "<button type=\"button\" class=\"k2b-calendar-week__moon\"", " style=\"", "\"><i class=\"ti ti-moon\" aria-hidden=\"true\"></i><i class=\"", "\" aria-hidden=\"true\"></i></button>", "</div><div class=\"k2b-calendar-week__lines\" aria-hidden=\"true\">", "", "", "</div>", "</div></div></div><p class=\"k2b-calendar-sr\"", ">", "</p>", "</div>"],
11
+ _tmpl$5 = ["<button type=\"button\" class=\"k2b-calendar-week__head\"", " data-stop=\"", "\"", " data-minute=\"-2\"", " style=\"", "\"><span class=\"k2b-calendar-week__weekday\">", "</span><span class=\"k2b-calendar-week__number\">", "</span><span class=\"k2b-calendar-week__extra\">", "</span><span class=\"k2b-calendar-week__load\" aria-hidden=\"true\">", "</span><span class=\"k2b-calendar-week__cheer\" aria-hidden=\"true\"><i class=\"ti ti-confetti\" aria-hidden=\"true\"></i><span class=\"k2b-calendar-week__cheer-long\">", "</span><span class=\"k2b-calendar-week__cheer-short\">", "</span></span></button>"],
12
+ _tmpl$6 = ["<span class=\"k2b-calendar-week__badge\">", "", "</span>"],
13
+ _tmpl$7 = ["<i class=\"", "\" aria-hidden=\"true\"></i>"],
14
+ _tmpl$8 = ["<span style=\"", "\"></span>"],
15
+ _tmpl$9 = ["<div class=\"k2b-calendar-week__range\" style=\"", "\" aria-hidden=\"true\"></div>"],
16
+ _tmpl$0 = ["<div class=\"k2b-calendar-week__slot\"", " style=\"", "\">", "", "</div>"],
17
+ _tmpl$1 = ["<span class=\"k2b-calendar-week__hour\"", " style=\"", "\" aria-hidden=\"true\">", "</span>"],
18
+ _tmpl$10 = ["<span class=\"k2b-calendar-week__hour\" data-night=\"true\" style=\"", "\" aria-hidden=\"true\">", "</span>"],
19
+ _tmpl$11 = ["<span class=\"k2b-calendar-week__line\" style=\"", "\"></span>"],
20
+ _tmpl$12 = ["<span class=\"k2b-calendar-week__fold\" style=\"", "\"></span>"],
21
+ _tmpl$13 = ["<span class=\"k2b-calendar-week__line\"", " style=\"", "\"></span>"],
22
+ _tmpl$14 = ["<div class=\"k2b-calendar-week__fold-cell\" style=\"", "\"><ul class=\"k2b-calendar-fold__chips\">", "</ul></div>"],
23
+ _tmpl$15 = ["<span class=\"k2b-calendar-hover\" style=\"", "\" aria-hidden=\"true\">", "</span>"],
24
+ _tmpl$16 = ["<div class=\"k2b-calendar-now\" style=\"", "\" aria-hidden=\"true\"></div>"],
25
+ _tmpl$17 = ["<div class=\"k2b-calendar-week__column\" data-calendar-column", " role=\"group\"", " style=\"", "\">", "", "", "", "", "</div>"],
26
+ _tmpl$18 = ["<li", " style=\"", "\">", "", "</li>"];
27
+ import { dates as calendar } from "@k2b/stdlib";
28
+ import { createComputed, createEffect, createMemo, createSignal, createUniqueId, For, on, onCleanup, onMount, Show, untrack } from "solid-js";
29
+ import { GESTURE_GAP_MS, snapToColumn, wheelAxis, wheelDelta, wheelPixels } from "./calendar-day-scroll";
30
+ import { addDayKeys, DAY_MINUTES, dayHeightTerms, dayKeyNumber, dayKeyRange, dayMinuteAt, dayY, dayYTerms, markerLanes, readDayGeometry, termsCss } from "./calendar-days";
31
+ import { layoutMonthWeek } from "./calendar-month-layout";
32
+ import { startCalendarPointerSession } from "./calendar-pointer";
33
+ import { EventChip, eventHref, followCalendarHref, textWidth } from "./calendar-shared";
34
+ import { createTimeView, dayBands, dayGaps, dayLoad, eventAccent, foldedEntries, gapAside, isNearNow, longDay, markerAbove, nightHours, reducedMotion, swallowNextClick, TimeBand, TimeCheck, TimeGap, TimeMarker, TimeOverlays, TimeQuickCreate, weekKeysOf } from "./calendar-time-view";
35
+
36
+ /** Room a bar keeps beside its title and date hint: padding, torn edge, gap, and an icon such as a task box. */
37
+ const HINT_SLACK_PX = 36;
38
+
39
+ /**
40
+ * The week view with `dayHours`: the loaded days side by side in one strip, seven on a wide screen and three on a
41
+ * phone. A trackpad, Shift with the wheel, or a finger scroll it sideways day by day; each gesture keeps the axis it
42
+ * started on, and the strip settles on a day edge. The arrows move by whole weeks.
43
+ */
44
+ export const CalendarWeekView = props => {
45
+ const view = createTimeView(props);
46
+ const [mounted, setMounted] = createSignal(false);
47
+ const [firstIndex, setFirstIndex] = createSignal(0);
48
+ const [visibleCount, setVisibleCount] = createSignal(7);
49
+ /** The week whose nights decide whether the night rows open, set by navigation, not by free scrolling. */
50
+ const [nightWeek, setNightWeek] = createSignal(untrack(view.anchorKey));
51
+ const [nightOverride, setNightOverride] = createSignal(null);
52
+ const [roving, setRoving] = createSignal("");
53
+ let scroller;
54
+ const helpId = createUniqueId();
55
+ const anchorWeek = () => weekKeysOf(view, view.anchorKey());
56
+ // The server and the first render draw only the shown week, so the strip never flashes the days before it.
57
+ const keys = createMemo(() => {
58
+ const {
59
+ first,
60
+ last
61
+ } = view.range();
62
+ if (mounted()) return dayKeyRange(first, last);
63
+ const week = anchorWeek();
64
+ return week[0] >= first && week[6] <= last ? week : dayKeyRange(first, last);
65
+ });
66
+ const indexOf = key => dayKeyNumber(key) - dayKeyNumber(keys()[0]);
67
+ const nightsOpen = createMemo(() => {
68
+ const override = nightOverride();
69
+ if (override !== null) return override;
70
+ return weekKeysOf(view, nightWeek()).some(key => view.dayOf(key).night);
71
+ });
72
+ createEffect(on(nightWeek, () => setNightOverride(null), {
73
+ defer: true
74
+ }));
75
+ const wake = () => view.wake();
76
+
77
+ // Columns and scrolling.
78
+ /** The exact width of a day column: columns are a seventh or a third of the strip, rarely whole pixels. */
79
+ const columnWidth = () => scroller?.querySelector("[data-calendar-column]")?.getBoundingClientRect().width ?? 0;
80
+ const gutterWidth = () => scroller?.querySelector(".k2b-calendar-week__gutter")?.offsetWidth ?? 0;
81
+ const leftOf = index => index * columnWidth();
82
+ const visibleIndex = () => {
83
+ const width = columnWidth();
84
+ return width > 0 && scroller ? Math.round(scroller.scrollLeft / width) : 0;
85
+ };
86
+ const visibleKeys = () => {
87
+ const first = keys()[Math.min(keys().length - 1, Math.max(0, firstIndex()))] ?? view.anchorKey();
88
+ return {
89
+ first,
90
+ last: addDayKeys(first, visibleCount() - 1)
91
+ };
92
+ };
93
+ /** Now shows in the hours while today is among the days in view, like the prototype's week. */
94
+ const nowShown = () => {
95
+ const {
96
+ first,
97
+ last
98
+ } = visibleKeys();
99
+ const today = view.nowKey();
100
+ return today >= first && today <= last && view.nowMinute() >= 0 && view.nowMinute() < (nightsOpen() ? DAY_MINUTES : wake());
101
+ };
102
+ let settling = false;
103
+ let glideTarget = null;
104
+ let glideTimer = 0;
105
+ const glideTo = (left, top) => {
106
+ if (!scroller) return;
107
+ const target = Math.max(0, Math.min(left, scroller.scrollWidth - scroller.clientWidth));
108
+ glideTarget = target;
109
+ window.clearTimeout(glideTimer);
110
+ glideTimer = window.setTimeout(() => {
111
+ glideTarget = null;
112
+ }, 1600);
113
+ scroller.scrollTo({
114
+ left: target,
115
+ ...(top === undefined ? {} : {
116
+ top
117
+ }),
118
+ behavior: reducedMotion() ? "instant" : "smooth"
119
+ });
120
+ };
121
+ /** The scroll position of a minute in the timeline, with now in the upper third. */
122
+ const topFor = minute => {
123
+ if (!scroller) return 0;
124
+ const body = scroller.querySelector("[data-calendar-body]");
125
+ const head = scroller.querySelector(".k2b-calendar-week__top");
126
+ if (!body || !head) return 0;
127
+ const geometry = readDayGeometry(body, wake(), nightsOpen());
128
+ const offset = body.getBoundingClientRect().top - scroller.getBoundingClientRect().top + scroller.scrollTop - head.offsetHeight;
129
+ return Math.max(0, offset + dayY(geometry, minute) - (scroller.clientHeight - head.offsetHeight) / 3);
130
+ };
131
+ /** The first column of a week: its first day, or on a phone a day of it kept inside the week. */
132
+ const columnFor = (key, phoneDay) => {
133
+ const week = weekKeysOf(view, key);
134
+ if (visibleCount() >= 7 || !phoneDay) return indexOf(week[0]);
135
+ const day = Math.max(0, Math.min(7 - visibleCount(), dayKeyNumber(phoneDay) - dayKeyNumber(week[0])));
136
+ return indexOf(week[day]);
137
+ };
138
+ const positionAt = key => {
139
+ if (!scroller) return;
140
+ measureCount();
141
+ const today = view.nowKey();
142
+ const inWeek = weekKeysOf(view, key).includes(today);
143
+ scroller.scrollLeft = leftOf(columnFor(key, key));
144
+ scroller.scrollTop = inWeek ? topFor(view.nowMinute()) : topFor(60);
145
+ lastLeft = scroller.scrollLeft;
146
+ syncFirst();
147
+ };
148
+ const measureCount = () => {
149
+ const width = columnWidth();
150
+ if (!scroller || width <= 0) return;
151
+ // Whole-pixel columns leave a few pixels at the right edge; the strip ends at its last whole day instead of
152
+ // showing a sliver of the next one.
153
+ const room = scroller.clientWidth + (Number.parseFloat(scroller.style.marginRight) || 0) - gutterWidth();
154
+ const count = Math.max(1, Math.round(room / width));
155
+ setVisibleCount(count);
156
+ const rest = Math.floor(room - count * width);
157
+ scroller.style.marginRight = rest > 0 ? `${rest}px` : "";
158
+ };
159
+ let reportTimer = 0;
160
+ let reported = "";
161
+ const syncFirst = () => {
162
+ const index = visibleIndex();
163
+ if (index !== untrack(firstIndex)) setFirstIndex(index);
164
+ if (!untrack(keys)[index]) return;
165
+ // The host learns the days the reader sees once they changed, after the scroll calmed down.
166
+ const {
167
+ first,
168
+ last
169
+ } = untrack(visibleKeys);
170
+ if (`${first}/${last}` === reported) return;
171
+ reported = `${first}/${last}`;
172
+ window.clearTimeout(reportTimer);
173
+ reportTimer = window.setTimeout(() => props.owner.onVisibleDateChange?.(view.parseKey(first), view.parseKey(last)), 250);
174
+ };
175
+
176
+ // A gesture keeps its axis; the strip settles on a day edge once the scroll rests.
177
+ let axis = null;
178
+ let lastWheel = 0;
179
+ let fingers = 0;
180
+ let restTimer = 0;
181
+ let ready = false;
182
+ /** Whether a wheel or a finger moved the strip since it last settled, and the way it last moved. */
183
+ let gesture = false;
184
+ let heading = 0;
185
+ let headingFrom = 0;
186
+ const settle = () => {
187
+ if (!scroller || fingers > 0 || settling || glideTarget !== null || view.dragging()) return;
188
+ const width = columnWidth();
189
+ const space = Number.parseFloat(getComputedStyle(scroller).scrollPaddingRight) || 0;
190
+ // A gesture rests on the nearest edge. A scroll without one, such as a reveal, goes on its way, so what it brought
191
+ // into view stays there; with end space, a revealed day can rest with its end beside the drawer.
192
+ const target = snapToColumn(scroller.scrollLeft, width, space > 0 ? scroller.clientWidth - gutterWidth() - space : undefined, gesture ? 0 : heading);
193
+ gesture = false;
194
+ if (Math.abs(target - scroller.scrollLeft) <= 0.5) return;
195
+ settling = true;
196
+ scroller.scrollTo({
197
+ left: target,
198
+ behavior: reducedMotion() ? "instant" : "smooth"
199
+ });
200
+ window.setTimeout(() => {
201
+ settling = false;
202
+ }, 450);
203
+ };
204
+ const onWheel = event => {
205
+ if (!scroller || event.ctrlKey || view.dragging()) return;
206
+ // A gesture that goes on takes over from a glide onto a day start; the strip settles again once it rests.
207
+ settling = false;
208
+ gesture = true;
209
+ if (event.timeStamp - lastWheel > GESTURE_GAP_MS || axis === null) axis = wheelAxis(event.deltaX, event.deltaY, event.shiftKey);
210
+ lastWheel = event.timeStamp;
211
+ const delta = wheelPixels(wheelDelta(axis, event.deltaX, event.deltaY, event.shiftKey), event.deltaMode, axis === "x" ? scroller.clientWidth : scroller.clientHeight);
212
+ if (axis === "x") {
213
+ event.preventDefault();
214
+ glideTarget = null;
215
+ scroller.scrollLeft += delta;
216
+ } else if (event.deltaX !== 0) {
217
+ // A trackpad's slight sideways drift in a vertical gesture would move the days; only the vertical part counts.
218
+ event.preventDefault();
219
+ scroller.scrollTop += delta;
220
+ }
221
+ };
222
+ let askedEarlier = "";
223
+ let askedLater = "";
224
+ /** The last scroll position the strip read, to tell which way the reader moves. */
225
+ let lastLeft = 0;
226
+ /**
227
+ * Asks for the week before or after once the reader moves within a week of an end. Days loaded before the strip push
228
+ * it on by a week, so only moving toward an end asks; a glide may load later days, which push nothing.
229
+ */
230
+ const loadMore = () => {
231
+ if (!scroller || !ready || view.dragging()) return;
232
+ const width = columnWidth();
233
+ const {
234
+ first,
235
+ last
236
+ } = view.range();
237
+ const left = scroller.scrollLeft;
238
+ const direction = Math.sign(left - lastLeft);
239
+ lastLeft = left;
240
+ if (direction < 0 && glideTarget === null && left < width * 7 - 1 && props.owner.onLoadEarlier && askedEarlier !== first) {
241
+ askedEarlier = first;
242
+ void props.owner.onLoadEarlier();
243
+ }
244
+ if (direction > 0 && left + scroller.clientWidth > scroller.scrollWidth - width * 7 + 1 && props.owner.onLoadLater && askedLater !== last) {
245
+ askedLater = last;
246
+ void props.owner.onLoadLater();
247
+ }
248
+ };
249
+ let frame = 0;
250
+ const onScroll = () => {
251
+ if (!scroller) return;
252
+ if (glideTarget !== null && Math.abs(scroller.scrollLeft - glideTarget) < 2) glideTarget = null;
253
+ if (Math.abs(scroller.scrollLeft - headingFrom) > 0.5) heading = Math.sign(scroller.scrollLeft - headingFrom);
254
+ headingFrom = scroller.scrollLeft;
255
+ if (!frame) frame = requestAnimationFrame(() => {
256
+ frame = 0;
257
+ syncFirst();
258
+ loadMore();
259
+ rememberAnchor();
260
+ });
261
+ window.clearTimeout(restTimer);
262
+ restTimer = window.setTimeout(settle, fingers > 0 ? 160 : 140);
263
+ };
264
+
265
+ // Days loaded before the strip's first day shift it; the view keeps its days in place.
266
+ let anchor = null;
267
+ /** A day the host moved to before its days loaded; it goes on screen once they arrive. */
268
+ let pendingTarget = null;
269
+ /** Notes the day at the start of the strip, read against the days the strip draws, which may still be the old ones. */
270
+ const rememberAnchor = (drawn = untrack(keys)) => {
271
+ if (!scroller) return;
272
+ const width = columnWidth();
273
+ if (width <= 0) return;
274
+ const index = Math.floor(scroller.scrollLeft / width);
275
+ const key = drawn[index];
276
+ if (key) anchor = {
277
+ key,
278
+ offset: scroller.scrollLeft - index * width
279
+ };
280
+ };
281
+ // Before the days change the strip, note its place from the page as it stands. A computation runs before any render
282
+ // effect touches the page, so no row has shrunk the strip yet.
283
+ createComputed(on(keys, (next, drawn) => {
284
+ if (!untrack(mounted) || !drawn) return;
285
+ rememberAnchor(drawn);
286
+ // Days that come in before the strip move every index; the first day in view stays the same day, so what
287
+ // depends on the days in view, such as the all-day row, never sees the days that came in.
288
+ const shift = dayKeyNumber(drawn[0]) - dayKeyNumber(next[0]);
289
+ if (shift !== 0) setFirstIndex(index => index + shift);
290
+ }, {
291
+ defer: true
292
+ }));
293
+ createEffect(on(keys, () => {
294
+ if (!untrack(mounted) || !scroller) return;
295
+ if (pendingTarget && loaded(pendingTarget)) {
296
+ positionAt(pendingTarget);
297
+ pendingTarget = null;
298
+ rememberAnchor();
299
+ syncFirst();
300
+ return;
301
+ }
302
+ if (!anchor) return;
303
+ const index = indexOf(anchor.key);
304
+ const left = leftOf(index) + anchor.offset;
305
+ if (Math.abs(scroller.scrollLeft - left) > 0.5) scroller.scrollLeft = left;
306
+ lastLeft = scroller.scrollLeft;
307
+ }, {
308
+ defer: true
309
+ }));
310
+
311
+ /** The day width the strip last kept its first day for. */
312
+ let measuredWidth = 0;
313
+ onMount(() => {
314
+ setMounted(true);
315
+ queueMicrotask(() => {
316
+ positionAt(view.anchorKey());
317
+ rememberAnchor();
318
+ ready = true;
319
+ });
320
+ const observer = typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver(() => {
321
+ if (!scroller) return;
322
+ // A reveal the scroll handler has not read yet wins over keeping the first day.
323
+ const unread = Math.abs(scroller.scrollLeft - lastLeft) > 0.5;
324
+ const before = untrack(firstIndex);
325
+ measureCount();
326
+ // Only days of a new width move the strip back onto its first day. Setting scrollLeft stops a smooth scroll
327
+ // on its way, such as a reveal, so a drawer's padding or the strip's own trim must not.
328
+ const width = columnWidth();
329
+ const resized = Math.abs(width - measuredWidth) > 0.01;
330
+ measuredWidth = width;
331
+ if (resized && !unread && Math.abs(scroller.scrollLeft - leftOf(before)) > 0.5) scroller.scrollLeft = leftOf(before);
332
+ lastLeft = scroller.scrollLeft;
333
+ syncFirst();
334
+ });
335
+ if (scroller) observer?.observe(scroller);
336
+ onCleanup(() => {
337
+ observer?.disconnect();
338
+ window.clearTimeout(restTimer);
339
+ window.clearTimeout(reportTimer);
340
+ window.clearTimeout(glideTimer);
341
+ if (frame) cancelAnimationFrame(frame);
342
+ });
343
+ });
344
+ createEffect(on(view.anchorKey, key => {
345
+ if (!untrack(mounted)) return;
346
+ setNightWeek(key);
347
+ pendingTarget = loaded(key) ? null : key;
348
+ if (pendingTarget) return;
349
+ if (key === untrack(visibleKeys).first) {
350
+ rememberAnchor();
351
+ return;
352
+ }
353
+ queueMicrotask(() => {
354
+ positionAt(key);
355
+ rememberAnchor();
356
+ });
357
+ }, {
358
+ defer: true
359
+ }));
360
+ const loaded = key => key >= view.range().first && key <= view.range().last;
361
+ const handle = {
362
+ visibleKeys,
363
+ step: direction => {
364
+ const first = visibleKeys().first;
365
+ const week = weekKeysOf(view, first);
366
+ // From the middle of a week the arrows go to the start of a week first.
367
+ const target = direction > 0 ? addDayKeys(week[0], 7) : first === week[0] ? addDayKeys(week[0], -7) : week[0];
368
+ const last = addDayKeys(target, Math.min(6, visibleCount() - 1));
369
+ if (!loaded(target) || !loaded(last)) return false;
370
+ setNightWeek(target);
371
+ glideTo(leftOf(indexOf(target)));
372
+ return true;
373
+ },
374
+ goTo: key => {
375
+ if (!loaded(key)) return false;
376
+ setNightWeek(key);
377
+ view.setSelection({
378
+ key
379
+ });
380
+ glideTo(leftOf(columnFor(key, key)));
381
+ return true;
382
+ },
383
+ today: () => {
384
+ const today = view.nowKey();
385
+ const week = weekKeysOf(view, today);
386
+ if (!loaded(week[0]) || !loaded(week[6])) return false;
387
+ setNightWeek(today);
388
+ queueMicrotask(() => glideTo(leftOf(columnFor(today, today)), topFor(view.nowMinute())));
389
+ return true;
390
+ },
391
+ todayDirection: () => {
392
+ const today = view.nowKey();
393
+ const {
394
+ first,
395
+ last
396
+ } = visibleKeys();
397
+ if (today < first) return "left";
398
+ if (today > last) return "right";
399
+ return "";
400
+ },
401
+ controller: {
402
+ quickCreate: create => {
403
+ const target = view.createTarget(visibleKeys());
404
+ const column = scroller?.querySelector(`[data-calendar-body="${target.key}"]`);
405
+ view.openQuickCreate(target.key, target.start, target.end, slotRect(target.key, target.start, target.end) ?? column ?? scroller, create);
406
+ },
407
+ createTarget: () => view.rangeOfSelection(view.createTarget(visibleKeys())),
408
+ dragIn: (event, item, onDrop) => view.dragIn(event, item, onDrop, scroller)
409
+ }
410
+ };
411
+ const slotRect = (key, start, end) => {
412
+ const body = scroller?.querySelector(`[data-calendar-body="${key}"]`);
413
+ if (!body || !scroller) return null;
414
+ const rect = body.getBoundingClientRect();
415
+ const bounds = scroller.getBoundingClientRect();
416
+ if (rect.right < bounds.left + gutterWidth() || rect.left > bounds.right) return null;
417
+ const geometry = readDayGeometry(body, wake(), nightsOpen());
418
+ const top = rect.top + dayY(geometry, start);
419
+ const bottom = rect.top + dayY(geometry, end);
420
+ if (bottom < bounds.top + 40 || top > bounds.bottom - 20) return null;
421
+ return new DOMRect(rect.left, Math.max(bounds.top, top), rect.width, Math.max(8, Math.min(bottom, bounds.bottom) - Math.max(bounds.top, top)));
422
+ };
423
+ onMount(() => props.register(handle));
424
+ onCleanup(() => props.register(null));
425
+
426
+ // The all-day row: one bar per week, with a torn edge where an event goes on in the week before or after.
427
+ const allDay = createMemo(() => {
428
+ const all = keys();
429
+ const spans = new Map();
430
+ for (const key of all) for (const span of view.dayOf(key).spans) spans.set(span.item, span);
431
+ const entries = [...spans.values()].sort((left, right) => left.firstKey.localeCompare(right.firstKey) || right.lastKey.localeCompare(left.lastKey)).map(span => ({
432
+ item: span,
433
+ firstKey: span.firstKey,
434
+ lastKey: span.lastKey,
435
+ bar: !span.marker
436
+ }));
437
+ const weeks = [];
438
+ for (let index = 0; index < all.length;) {
439
+ const week = weekKeysOf(view, all[index]).filter(key => key >= all[0] && key <= all[all.length - 1]);
440
+ const offset = indexOf(week[0]);
441
+ weeks.push({
442
+ offset,
443
+ keys: week
444
+ });
445
+ index = offset + week.length;
446
+ }
447
+ return weeks.map(week => {
448
+ const layout = layoutMonthWeek(week.keys, entries);
449
+ const lanes = Math.max(0, ...layout.bars.map(bar => bar.lane + 1));
450
+ return {
451
+ ...week,
452
+ layout,
453
+ rows: lanes + Math.max(0, ...layout.singles.map(singles => singles.length))
454
+ };
455
+ });
456
+ });
457
+ /**
458
+ * The rows the weeks in view need, as in a week of its own. A week that loads off screen never changes them, so the
459
+ * hours stay where they are; its entries below these rows wait hidden until it comes into view.
460
+ */
461
+ const allDayRows = createMemo(() => {
462
+ const first = firstIndex();
463
+ const last = first + visibleCount() - 1;
464
+ return Math.max(1, ...allDay().filter(week => week.offset <= last && week.offset + week.keys.length - 1 >= first).map(week => week.rows));
465
+ });
466
+
467
+ // Keyboard: one tab stop in the grid; the arrows move between days, entries, and free time. Only what is drawn can
468
+ // take the focus.
469
+ const stops = () => [...(scroller?.querySelectorAll("[data-stop]") ?? [])].filter(stop => stop.getClientRects().length > 0);
470
+ const focusDayKey = () => {
471
+ const current = view.selection()?.key;
472
+ const {
473
+ first,
474
+ last
475
+ } = visibleKeys();
476
+ if (current && current >= first && current <= last) return current;
477
+ return view.nowKey() >= first && view.nowKey() <= last ? view.nowKey() : first;
478
+ };
479
+ const rovingId = () => roving() || `h:${focusDayKey()}`;
480
+ const tabOf = id => rovingId() === id ? 0 : -1;
481
+ // An entry that leaves the days gives the grid's tab stop back to a heading after the page has updated.
482
+ createEffect(on(view.days, () => {
483
+ queueMicrotask(() => {
484
+ if (!scroller || !roving()) return;
485
+ if ([...scroller.querySelectorAll("[data-stop]")].some(stop => stop.dataset.stop === roving())) return;
486
+ setRoving("");
487
+ if (!document.activeElement || document.activeElement === document.body) {
488
+ scroller.querySelector(`[data-stop="h:${focusDayKey()}"]`)?.focus({
489
+ preventScroll: true
490
+ });
491
+ }
492
+ });
493
+ }, {
494
+ defer: true
495
+ }));
496
+ const onKeyDown = event => {
497
+ const current = event.target instanceof HTMLElement ? event.target.closest("[data-stop]") : null;
498
+ if (!current || event.altKey || event.metaKey || event.ctrlKey) return;
499
+ const direction = {
500
+ ArrowUp: "up",
501
+ ArrowDown: "down",
502
+ ArrowLeft: "left",
503
+ ArrowRight: "right",
504
+ Home: "home",
505
+ End: "end"
506
+ }[event.key];
507
+ if (!direction) return;
508
+ event.preventDefault();
509
+ const all = stops();
510
+ const column = Number(current.dataset.col);
511
+ const minute = Number(current.dataset.minute);
512
+ const inColumn = index => all.filter(stop => Number(stop.dataset.col) === index).sort((left, right) => Number(left.dataset.minute) - Number(right.dataset.minute));
513
+ const list = inColumn(column);
514
+ const position = list.indexOf(current);
515
+ let next;
516
+ if (direction === "down") next = list[position + 1];else if (direction === "up") next = list[position - 1];else if (direction === "home") next = list[0];else if (direction === "end") next = list[list.length - 1];else {
517
+ const other = inColumn(column + (direction === "left" ? -1 : 1));
518
+ next = other.reduce((best, stop) => Math.abs(Number(stop.dataset.minute) - minute) < Math.abs(Number(best?.dataset.minute ?? Number.POSITIVE_INFINITY) - minute) ? stop : best, undefined);
519
+ }
520
+ if (!next) return;
521
+ setRoving(next.dataset.stop ?? "");
522
+ next.focus({
523
+ preventScroll: Boolean(next.closest(".k2b-calendar-week__top"))
524
+ });
525
+ const index = Number(next.dataset.col);
526
+ const first = untrack(firstIndex);
527
+ if (index < first) glideTo(leftOf(index));else if (index >= first + visibleCount()) glideTo(leftOf(index - visibleCount() + 1));
528
+ };
529
+ const openDay = key => {
530
+ const href = props.owner.views && !props.owner.views.includes("day") ? undefined : props.owner.getDateHref?.(view.parseKey(key), "day");
531
+ if (href) followCalendarHref(props.owner, href);else {
532
+ props.owner.onViewChange?.("day");
533
+ props.owner.onDateChange?.(view.parseKey(key), "day");
534
+ }
535
+ };
536
+ let lastPointer = "mouse";
537
+
538
+ // The all-day row: drag across days for one all-day event; a double-click creates on one day.
539
+ const startAllDayRange = (pointerEvent, row) => {
540
+ if (pointerEvent.pointerType !== "mouse" || pointerEvent.button !== 0 || !view.canCreate()) return;
541
+ const keyAt = clientX => {
542
+ const width = columnWidth();
543
+ const rect = row.getBoundingClientRect();
544
+ const index = Math.max(0, Math.min(keys().length - 1, Math.floor((clientX - rect.left) / width)));
545
+ return keys()[index];
546
+ };
547
+ const origin = keyAt(pointerEvent.clientX);
548
+ startCalendarPointerSession({
549
+ event: pointerEvent,
550
+ resolve: clientX => keyAt(clientX),
551
+ onPreview: key => setRangeSelection(origin < key ? {
552
+ first: origin,
553
+ last: key
554
+ } : {
555
+ first: key,
556
+ last: origin
557
+ }),
558
+ onCommit: key => {
559
+ swallowNextClick();
560
+ const range = origin < key ? {
561
+ first: origin,
562
+ last: key
563
+ } : {
564
+ first: key,
565
+ last: origin
566
+ };
567
+ const cells = scroller?.querySelector(".k2b-calendar-week__range");
568
+ setRangeSelection(null);
569
+ const start = view.parseKey(range.first);
570
+ const end = calendar.addDays(view.parseKey(range.last), 1, view.dateConfig());
571
+ if (props.owner.renderQuickCreate) view.setQuick({
572
+ range: {
573
+ start,
574
+ end,
575
+ allDay: true
576
+ },
577
+ anchor: cells?.getBoundingClientRect() ?? row,
578
+ key: range.first
579
+ });else props.owner.onSlotActivate?.({
580
+ start,
581
+ end,
582
+ allDay: true
583
+ });
584
+ },
585
+ onCancel: () => setRangeSelection(null)
586
+ });
587
+ };
588
+ const [rangeSelection, setRangeSelection] = createSignal(null);
589
+ return _$ssr(_tmpl$4, _$ssrAttribute("data-dragging", _$escape(view.dragging() || undefined, true), false), _$ssrAttribute("aria-label", _$escape(view.messages().calendarWeek, true), false) + _$ssrAttribute("aria-describedby", _$escape(helpId, true), false), _$ssrStyleProperty("--k2b-calendar-days:", _$escape(String(keys().length), true)), _$escape(view.messages().calendarWeekNumber({
590
+ week: isoWeek(visibleKeys().first)
591
+ })), _$escape(_$createComponent(For, {
592
+ get each() {
593
+ return keys();
594
+ },
595
+ children: key => {
596
+ const today = () => key === view.calendarToday();
597
+ const selected = () => {
598
+ const current = view.selection();
599
+ return Boolean(current && current.key === key && current.start === undefined);
600
+ };
601
+ const badge = () => props.owner.dayBadges?.[key];
602
+ const load = () => dayLoad(view, key);
603
+ const date = () => view.parseKey(key);
604
+ return _$ssr(_tmpl$5, _$ssrAttribute("data-calendar-head", _$escape(key, true), false), `h:${_$escape(key, true)}`, _$ssrAttribute("data-col", _$escape(indexOf(key), true), false), _$ssrAttribute("tabindex", _$escape(tabOf(`h:${key}`), true), false) + _$ssrAttribute("aria-pressed", selected() ? "true" : "false", false) + _$ssrAttribute("aria-label", _$escape([longDay(view, key), today() ? view.props.labels.today : "", badge()?.label ?? ""].filter(Boolean).join(", "), true), false) + _$ssrAttribute("data-today", today() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-past", key < view.calendarToday() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-cheer", view.cheered() === key ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("grid-column:", _$escape(String(indexOf(key) + 2), true)), _$escape(date().toLocaleDateString(view.dateConfig().locale ?? "en", {
605
+ weekday: "short",
606
+ timeZone: view.dateConfig().timeZone
607
+ }).replace(/\.$/, "")), _$escape(calendar.formatDayNumber(date(), view.dateConfig())), _$escape(_$createComponent(Show, {
608
+ get when() {
609
+ return badge();
610
+ },
611
+ children: current => _$ssr(_tmpl$6, _$escape(_$createComponent(Show, {
612
+ get when() {
613
+ return current().icon;
614
+ },
615
+ children: icon => _$ssr(_tmpl$7, `ti ti-${_$escape(icon(), true)}`)
616
+ })), _$escape(current().label))
617
+ })), _$escape(_$createComponent(For, {
618
+ get each() {
619
+ return load();
620
+ },
621
+ children: piece => _$ssr(_tmpl$8, _$ssrStyleProperty("width:", `${(_$escape(piece.end, true) - _$escape(piece.start, true)) / _$escape(wake(), true) * 100}%`) + _$ssrStyleProperty(";background:", _$escape(piece.color, true)))
622
+ })), _$escape(view.messages().calendarAllDone), _$escape(view.messages().calendarAllDoneShort));
623
+ }
624
+ })), _$escape(view.messages().calendarAllDayRow), _$escape(view.messages().calendarAllDayRowShort), _$ssrAttribute("aria-label", _$escape(view.messages().calendarAllDayAndDue, true), false), _$ssrStyleProperty("grid-template-rows:", `repeat(${_$escape(allDayRows(), true)}, var(--k2b-calendar-all-day-row))`), _$escape(_$createComponent(Show, {
625
+ get when() {
626
+ return rangeSelection();
627
+ },
628
+ children: range => _$ssr(_tmpl$9, _$ssrStyleProperty("grid-column:", `${_$escape(indexOf(range().first), true) + 1} / ${_$escape(indexOf(range().last), true) + 2}`) + _$ssrStyleProperty(";grid-row:", "1 / -1"))
629
+ })), _$escape(_$createComponent(For, {
630
+ get each() {
631
+ return allDay();
632
+ },
633
+ children: week => [_$createComponent(For, {
634
+ get each() {
635
+ return week.layout.bars;
636
+ },
637
+ children: bar => {
638
+ const span = bar.endColumn - bar.startColumn + 1;
639
+ const event = bar.item.item;
640
+ const checked = () => view.checkedOf(event);
641
+ const hint = (target, kind, before = "") => {
642
+ const text = kind === "from" ? view.messages().calendarContinuesFrom(hintDate(view, target, visibleKeys().first)) : view.messages().calendarContinuesUntil(hintDate(view, target, visibleKeys().first));
643
+ const font = `600 11px ${typeof document === "undefined" ? "sans-serif" : getComputedStyle(document.body).fontFamily}`;
644
+ return textWidth(`${event.title}${before}`, font) + textWidth(text, font) + HINT_SLACK_PX <= span * columnWidthSignal() ? text : undefined;
645
+ };
646
+ const start = () => bar.continuesBefore ? hint(bar.item.firstKey, "from") : undefined;
647
+ const end = () => bar.continuesAfter ? hint(bar.item.lastKey, "until", start() ?? "") : undefined;
648
+ return _$ssr(_tmpl$0, _$ssrAttribute("data-checkbox", checked() !== undefined ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-checked", checked() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-just-checked", view.justChecked().has(event.id) ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("grid-column:", `${_$escape(week.offset, true) + _$escape(bar.startColumn, true) + 1} / ${_$escape(week.offset, true) + _$escape(bar.endColumn, true) + 2}`) + _$ssrStyleProperty(";grid-row:", _$escape(String(bar.lane + 1), true)) + _$ssrStyleProperty(";--k2b-calendar-accent:", _$escape(eventAccent(event), true)) + _$ssrStyleProperty(";display:", bar.lane + 1 > allDayRows() ? "none" : _$escape(undefined, true)), _$escape(_$createComponent(Show, {
649
+ get when() {
650
+ return checked() !== undefined;
651
+ },
652
+ get children() {
653
+ return _$createComponent(TimeCheck, {
654
+ view: view,
655
+ event: event,
656
+ tabIndex: -1
657
+ });
658
+ }
659
+ })), _$escape(_$createComponent(EventChip, {
660
+ get event() {
661
+ return view.normalized(event, bar.item.firstKey);
662
+ },
663
+ get owner() {
664
+ return props.owner;
665
+ },
666
+ get href() {
667
+ return eventHref(props.owner, event);
668
+ },
669
+ get label() {
670
+ return spanLabel(view, bar.item);
671
+ },
672
+ compact: true,
673
+ timeline: true,
674
+ bar: true,
675
+ get checkbox() {
676
+ return checked() !== undefined;
677
+ },
678
+ get onCheck() {
679
+ return view.checkOf(event);
680
+ },
681
+ get continuesBefore() {
682
+ return bar.continuesBefore;
683
+ },
684
+ get continuesAfter() {
685
+ return bar.continuesAfter;
686
+ },
687
+ get hintStart() {
688
+ return start();
689
+ },
690
+ get hintEnd() {
691
+ return end();
692
+ },
693
+ get tabIndex() {
694
+ return tabOf(`a:${event.id}:${week.offset}`);
695
+ },
696
+ get stop() {
697
+ return {
698
+ id: `a:${event.id}:${week.offset}`,
699
+ column: week.offset + bar.startColumn,
700
+ minute: -1 + bar.lane / 100
701
+ };
702
+ }
703
+ })));
704
+ }
705
+ }), _$createComponent(For, {
706
+ get each() {
707
+ return week.layout.singles;
708
+ },
709
+ children: (singles, column) => {
710
+ const lanes = () => Math.max(0, ...week.layout.bars.filter(bar => bar.startColumn <= column() && bar.endColumn >= column()).map(bar => bar.lane + 1));
711
+ return _$createComponent(For, {
712
+ each: singles,
713
+ children: (span, row) => {
714
+ const event = span.item;
715
+ const checked = () => view.checkedOf(event);
716
+ const id = `a:${event.id}:${week.offset}`;
717
+ return _$ssr(_tmpl$0, _$ssrAttribute("data-checkbox", checked() !== undefined ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-checked", checked() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-just-checked", view.justChecked().has(event.id) ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("grid-column:", _$escape(String(week.offset + column() + 1), true)) + _$ssrStyleProperty(";grid-row:", _$escape(String(lanes() + row() + 1), true)) + _$ssrStyleProperty(";--k2b-calendar-accent:", _$escape(eventAccent(event), true)) + _$ssrStyleProperty(";display:", lanes() + row() + 1 > allDayRows() ? "none" : _$escape(undefined, true)), _$escape(_$createComponent(Show, {
718
+ get when() {
719
+ return checked() !== undefined;
720
+ },
721
+ get children() {
722
+ return _$createComponent(TimeCheck, {
723
+ view: view,
724
+ event: event,
725
+ tabIndex: -1
726
+ });
727
+ }
728
+ })), _$escape(_$createComponent(EventChip, {
729
+ get event() {
730
+ return view.normalized(event, span.firstKey);
731
+ },
732
+ get owner() {
733
+ return props.owner;
734
+ },
735
+ get href() {
736
+ return eventHref(props.owner, event);
737
+ },
738
+ compact: true,
739
+ timeline: true,
740
+ get checkbox() {
741
+ return checked() !== undefined;
742
+ },
743
+ get onCheck() {
744
+ return view.checkOf(event);
745
+ },
746
+ get tabIndex() {
747
+ return tabOf(id);
748
+ },
749
+ get stop() {
750
+ return {
751
+ id,
752
+ column: week.offset + column(),
753
+ minute: -1 + (lanes() + row()) / 100
754
+ };
755
+ }
756
+ })));
757
+ }
758
+ });
759
+ }
760
+ })]
761
+ })), _$ssrStyleProperty("height:", _$escape(termsCss(dayHeightTerms(wake(), nightsOpen())), true)), _$escape(_$createComponent(For, {
762
+ get each() {
763
+ return Array.from({
764
+ length: wake() / 60 + 1
765
+ }, (_, index) => index * 60);
766
+ },
767
+ children: minute => _$ssr(_tmpl$1, _$ssrAttribute("data-near-now", nowShown() && isNearNow(view, view.nowKey(), minute) ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("top:", _$escape(termsCss(dayYTerms(wake(), nightsOpen(), minute)), true)), _$escape(view.clock(minute)))
768
+ })), _$escape(_$createComponent(Show, {
769
+ get when() {
770
+ return nightsOpen();
771
+ },
772
+ get children() {
773
+ return _$createComponent(For, {
774
+ get each() {
775
+ return nightHours(wake());
776
+ },
777
+ children: minute => _$ssr(_tmpl$10, _$ssrStyleProperty("top:", _$escape(termsCss(dayYTerms(wake(), true, minute)), true)), _$escape(view.clock(minute)))
778
+ });
779
+ }
780
+ })), _$ssrAttribute("aria-expanded", nightsOpen() ? "true" : "false", false) + _$ssrAttribute("aria-label", nightsOpen() ? _$escape(view.messages().calendarFoldNights, true) : _$escape(view.messages().calendarOpenNights({
781
+ from: view.clock(wake()).slice(0, 2),
782
+ to: view.clock(DAY_MINUTES).slice(0, 2)
783
+ }), true), false) + _$ssrAttribute("title", nightsOpen() ? _$escape(view.messages().calendarFoldNights, true) : _$escape(view.messages().calendarOpenNights({
784
+ from: view.clock(wake()).slice(0, 2),
785
+ to: view.clock(DAY_MINUTES).slice(0, 2)
786
+ }), true), false) + _$ssrAttribute("data-open", nightsOpen() ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("top:", _$escape(termsCss(dayYTerms(wake(), nightsOpen(), nightsOpen() ? wake() : wake() + 1)), true)), `ti ${nightsOpen() ? "ti-chevron-up" : "ti-chevron-down"}`, _$escape(_$createComponent(Show, {
787
+ get when() {
788
+ return nowShown();
789
+ },
790
+ get children() {
791
+ return _$ssr(_tmpl$, _$ssrStyleProperty("top:", _$escape(termsCss(dayYTerms(wake(), nightsOpen(), view.nowMinute())), true)), _$escape(view.clock(view.nowMinute())));
792
+ }
793
+ })), _$escape(_$createComponent(For, {
794
+ get each() {
795
+ return Array.from({
796
+ length: wake() / 60 + 1
797
+ }, (_, index) => index * 60);
798
+ },
799
+ children: minute => _$ssr(_tmpl$11, _$ssrStyleProperty("top:", _$escape(termsCss(dayYTerms(wake(), nightsOpen(), minute)), true)))
800
+ })), _$escape(_$createComponent(Show, {
801
+ get when() {
802
+ return nightsOpen();
803
+ },
804
+ get fallback() {
805
+ return _$ssr(_tmpl$12, _$ssrStyleProperty("top:", `calc(${_$escape(termsCss(dayYTerms(wake(), false, wake())), true)} + 3px)`));
806
+ },
807
+ get children() {
808
+ return [_$ssr(_tmpl$2, _$ssrStyleProperty("top:", _$escape(termsCss(dayYTerms(wake(), true, wake())), true)) + _$ssrStyleProperty(";height:", _$escape(termsCss([0, (DAY_MINUTES - wake()) / 60, 1, 0]), true))), _$createComponent(For, {
809
+ get each() {
810
+ return nightHours(wake());
811
+ },
812
+ children: minute => _$ssr(_tmpl$13, _$ssrAttribute("data-midnight", view.clock(minute) === "00:00" ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("top:", _$escape(termsCss(dayYTerms(wake(), true, minute)), true)))
813
+ })];
814
+ }
815
+ })), _$escape(_$createComponent(Show, {
816
+ get when() {
817
+ return nowShown();
818
+ },
819
+ get children() {
820
+ return _$ssr(_tmpl$3, _$ssrStyleProperty("top:", _$escape(termsCss(dayYTerms(wake(), nightsOpen(), view.nowMinute())), true)));
821
+ }
822
+ })), _$escape(_$createComponent(For, {
823
+ get each() {
824
+ return keys();
825
+ },
826
+ children: key => _$createComponent(WeekColumn, {
827
+ view: view,
828
+ dayKey: key,
829
+ get column() {
830
+ return indexOf(key);
831
+ },
832
+ get expanded() {
833
+ return nightsOpen();
834
+ },
835
+ tabOf: tabOf,
836
+ lastPointer: () => lastPointer,
837
+ setLastPointer: value => {
838
+ lastPointer = value;
839
+ },
840
+ scroller: () => scroller
841
+ })
842
+ })), _$ssrAttribute("id", _$escape(helpId, true), false), _$escape(view.messages().calendarKeyboardHelp), _$escape(_$createComponent(TimeQuickCreate, {
843
+ view: view
844
+ })));
845
+ function columnWidthSignal() {
846
+ // Read through the visible count so a resize recomputes the hints.
847
+ visibleCount();
848
+ return columnWidth();
849
+ }
850
+ };
851
+ const WeekColumn = props => {
852
+ const view = props.view;
853
+ const model = createMemo(() => view.dayOf(props.dayKey));
854
+ const wake = () => view.wake();
855
+ const bands = createMemo(() => dayBands(view, props.dayKey, props.expanded));
856
+ const gaps = createMemo(() => dayGaps(view, props.dayKey));
857
+ const markers = () => model().markers.filter(marker => marker.minute < (props.expanded ? DAY_MINUTES : wake()));
858
+ /** Deadlines whose pills would cover each other share the width; keyed by marker, so rows keep their elements. */
859
+ const lanes = createMemo(() => new Map(markerLanes(markers(), wake()).map(entry => [entry.item, entry])));
860
+ const folded = () => props.expanded ? [] : foldedEntries(view, props.dayKey);
861
+ const isEmptyTarget = target => target instanceof Element && !target.closest("a,button,[data-calendar-event],.k2b-calendar-band,.k2b-calendar-marker,.k2b-calendar-draft,.k2b-calendar-week__fold-cell");
862
+ const minuteAt = (element, clientY) => {
863
+ const geometry = readDayGeometry(element, wake(), props.expanded);
864
+ return Math.max(0, Math.min((props.expanded ? DAY_MINUTES : wake()) - 15, dayMinuteAt(geometry, clientY - element.getBoundingClientRect().top)));
865
+ };
866
+ const nowMinute = () => props.dayKey === view.nowKey() ? view.nowMinute() : null;
867
+ const selected = () => view.selection()?.key === props.dayKey;
868
+ /** The quarter hour under the mouse on empty time, as a line with its time, to aim a drag at. */
869
+ const [hover, setHover] = createSignal(null);
870
+ const timeline = createMemo(() => [...gaps().map(gap => ({
871
+ at: gap.start,
872
+ kind: "gap",
873
+ gap
874
+ })), ...bands().map(band => ({
875
+ at: Math.max(0, band.start),
876
+ kind: "band",
877
+ band
878
+ })), ...markers().map(marker => ({
879
+ at: marker.minute,
880
+ kind: "marker",
881
+ marker
882
+ }))].sort((left, right) => left.at - right.at));
883
+ return _$ssr(_tmpl$17, _$ssrAttribute("data-calendar-body", _$escape(props.dayKey, true), false) + _$ssrAttribute("data-expanded", props.expanded ? "true" : "false", false) + _$ssrAttribute("data-today", props.dayKey === view.calendarToday() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-selected", selected() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-active", view.activeKey() === props.dayKey ? "true" : _$escape(undefined, true), false), _$ssrAttribute("aria-label", _$escape(longDay(view, props.dayKey), true), false), _$ssrStyleProperty("grid-column:", _$escape(String(props.column + 2), true)) + _$ssrStyleProperty(";height:", _$escape(termsCss(dayHeightTerms(wake(), props.expanded)), true)), _$escape(_$createComponent(For, {
884
+ get each() {
885
+ return timeline();
886
+ },
887
+ children: entry => entry.kind === "gap" ? _$createComponent(TimeGap, {
888
+ view: view,
889
+ get gap() {
890
+ return entry.gap;
891
+ },
892
+ get dayKey() {
893
+ return props.dayKey;
894
+ },
895
+ get expanded() {
896
+ return props.expanded;
897
+ },
898
+ get aside() {
899
+ return gapAside(model(), entry.gap);
900
+ },
901
+ get tabIndex() {
902
+ return props.tabOf(`g:${props.dayKey}:${entry.gap.start}`);
903
+ },
904
+ get stop() {
905
+ return {
906
+ id: `g:${props.dayKey}:${entry.gap.start}`,
907
+ column: props.column,
908
+ minute: entry.gap.start + 0.5
909
+ };
910
+ }
911
+ }) : entry.kind === "band" ? _$createComponent(TimeBand, {
912
+ view: view,
913
+ get band() {
914
+ return entry.band;
915
+ },
916
+ get dayKey() {
917
+ return props.dayKey;
918
+ },
919
+ get expanded() {
920
+ return props.expanded;
921
+ },
922
+ hourPx: 48,
923
+ get tabIndex() {
924
+ return props.tabOf(`b:${entry.band.item.id}:${props.dayKey}`);
925
+ },
926
+ get stop() {
927
+ return {
928
+ id: `b:${entry.band.item.id}:${props.dayKey}`,
929
+ column: props.column,
930
+ minute: entry.band.start
931
+ };
932
+ },
933
+ get scroller() {
934
+ return props.scroller;
935
+ }
936
+ }) : _$createComponent(TimeMarker, {
937
+ view: view,
938
+ get event() {
939
+ return entry.marker.item;
940
+ },
941
+ get minute() {
942
+ return entry.marker.minute;
943
+ },
944
+ get dayKey() {
945
+ return props.dayKey;
946
+ },
947
+ get expanded() {
948
+ return props.expanded;
949
+ },
950
+ get above() {
951
+ return markerAbove(model(), entry.marker.minute);
952
+ },
953
+ get lane() {
954
+ return lanes().get(entry.marker)?.lane ?? 0;
955
+ },
956
+ get lanes() {
957
+ return lanes().get(entry.marker)?.lanes ?? 1;
958
+ },
959
+ showTime: false,
960
+ get tabIndex() {
961
+ return props.tabOf(`m:${entry.marker.item.id}`);
962
+ },
963
+ get stop() {
964
+ return {
965
+ id: `m:${entry.marker.item.id}`,
966
+ column: props.column,
967
+ minute: entry.marker.minute
968
+ };
969
+ },
970
+ get scroller() {
971
+ return props.scroller;
972
+ }
973
+ })
974
+ })), _$escape(_$createComponent(Show, {
975
+ get when() {
976
+ return _$memo(() => !!!props.expanded)() && folded().length > 0;
977
+ },
978
+ get children() {
979
+ return _$ssr(_tmpl$14, _$ssrStyleProperty("top:", `calc(${_$escape(termsCss(dayYTerms(wake(), false, wake())), true)} + 3px)`), _$escape(_$createComponent(For, {
980
+ get each() {
981
+ return folded();
982
+ },
983
+ children: (entry, index) => {
984
+ const checked = () => view.checkedOf(entry.event);
985
+ return _$ssr(_tmpl$18, _$ssrAttribute("data-checkbox", checked() !== undefined ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-checked", checked() ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("--k2b-calendar-accent:", _$escape(eventAccent(entry.event), true)), _$escape(_$createComponent(Show, {
986
+ get when() {
987
+ return checked() !== undefined;
988
+ },
989
+ get children() {
990
+ return _$createComponent(TimeCheck, {
991
+ view: view,
992
+ get event() {
993
+ return entry.event;
994
+ },
995
+ tabIndex: -1
996
+ });
997
+ }
998
+ })), _$escape(_$createComponent(EventChip, {
999
+ get event() {
1000
+ return view.normalized(entry.event, props.dayKey);
1001
+ },
1002
+ get owner() {
1003
+ return view.props.owner;
1004
+ },
1005
+ get href() {
1006
+ return eventHref(view.props.owner, entry.event);
1007
+ },
1008
+ compact: true,
1009
+ timeline: true,
1010
+ get leadingTime() {
1011
+ return entry.time;
1012
+ },
1013
+ get checkbox() {
1014
+ return checked() !== undefined;
1015
+ },
1016
+ get onCheck() {
1017
+ return view.checkOf(entry.event);
1018
+ },
1019
+ get tabIndex() {
1020
+ return props.tabOf(`f:${entry.event.id}:${props.dayKey}`);
1021
+ },
1022
+ get stop() {
1023
+ return {
1024
+ id: `f:${entry.event.id}:${props.dayKey}`,
1025
+ column: props.column,
1026
+ minute: wake() + index() / 100
1027
+ };
1028
+ }
1029
+ })));
1030
+ }
1031
+ })));
1032
+ }
1033
+ })), _$escape(_$createComponent(TimeOverlays, {
1034
+ view: view,
1035
+ get dayKey() {
1036
+ return props.dayKey;
1037
+ },
1038
+ get expanded() {
1039
+ return props.expanded;
1040
+ }
1041
+ })), _$escape(_$createComponent(Show, {
1042
+ get when() {
1043
+ return hover() !== null;
1044
+ },
1045
+ get children() {
1046
+ return _$ssr(_tmpl$15, _$ssrStyleProperty("top:", _$escape(termsCss(dayYTerms(wake(), props.expanded, hover())), true)), _$escape(view.clock(hover())));
1047
+ }
1048
+ })), _$escape(_$createComponent(Show, {
1049
+ get when() {
1050
+ return _$memo(() => !!(nowMinute() !== null && nowMinute() >= 0))() && nowMinute() < (props.expanded ? DAY_MINUTES : wake());
1051
+ },
1052
+ get children() {
1053
+ return _$ssr(_tmpl$16, _$ssrStyleProperty("top:", _$escape(termsCss(dayYTerms(wake(), props.expanded, nowMinute())), true)));
1054
+ }
1055
+ })));
1056
+ };
1057
+
1058
+ /** ISO week number of a day key. */
1059
+ const isoWeek = key => {
1060
+ const date = new Date(`${key}T00:00:00Z`);
1061
+ const day = date.getUTCDay() || 7;
1062
+ date.setUTCDate(date.getUTCDate() + 4 - day);
1063
+ const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
1064
+ return Math.ceil(((date.getTime() - yearStart.getTime()) / 86_400_000 + 1) / 7);
1065
+ };
1066
+
1067
+ /** The day at a torn end of a bar, with its month when it lies in another month than the shown week. */
1068
+ const hintDate = (view, target, shown) => ({
1069
+ day: Number(target.slice(8, 10)),
1070
+ month: target.slice(0, 7) === shown.slice(0, 7) ? undefined : view.parseKey(target).toLocaleDateString(view.dateConfig().locale ?? "en", {
1071
+ month: "short",
1072
+ timeZone: view.dateConfig().timeZone
1073
+ }).replace(/\.$/, "")
1074
+ });
1075
+
1076
+ /** A long entry's accessible name names the days it spans. */
1077
+ const spanLabel = (view, span) => {
1078
+ if (span.firstKey === span.lastKey) return undefined;
1079
+ const format = key => view.parseKey(key).toLocaleDateString(view.dateConfig().locale ?? "en", {
1080
+ month: "long",
1081
+ day: "numeric",
1082
+ timeZone: view.dateConfig().timeZone
1083
+ });
1084
+ return view.messages().calendarEventTime({
1085
+ title: span.item.title,
1086
+ start: format(span.firstKey),
1087
+ end: format(span.lastKey)
1088
+ });
1089
+ };
1090
+ //# sourceMappingURL=CalendarWeekView.js.map