@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,1654 @@
1
+ import { template as _$template } from "solid-js/web";
2
+ import { delegateEvents as _$delegateEvents } from "solid-js/web";
3
+ import { use as _$use } from "solid-js/web";
4
+ import { style as _$style } from "solid-js/web";
5
+ import { setStyleProperty as _$setStyleProperty } from "solid-js/web";
6
+ import { getNextMarker as _$getNextMarker } from "solid-js/web";
7
+ import { memo as _$memo } from "solid-js/web";
8
+ import { createComponent as _$createComponent } from "solid-js/web";
9
+ import { setAttribute as _$setAttribute } from "solid-js/web";
10
+ import { effect as _$effect } from "solid-js/web";
11
+ import { runHydrationEvents as _$runHydrationEvents } from "solid-js/web";
12
+ import { insert as _$insert } from "solid-js/web";
13
+ import { getNextElement as _$getNextElement } from "solid-js/web";
14
+ var _tmpl$ = /*#__PURE__*/_$template(`<svg viewBox="0 0 16 16"aria-hidden=true><rect x=1.75 y=1.75 width=12.5 height=12.5 rx=3.5></rect><path d="M4.6 8.3l2.3 2.3 4.6-4.9">`),
15
+ _tmpl$2 = /*#__PURE__*/_$template(`<button type=button class=k2b-calendar-check role=checkbox>`),
16
+ _tmpl$3 = /*#__PURE__*/_$template(`<span class=k2b-calendar-check aria-hidden=true>`),
17
+ _tmpl$4 = /*#__PURE__*/_$template(`<span class=k2b-calendar-band__fill aria-hidden=true>`),
18
+ _tmpl$5 = /*#__PURE__*/_$template(`<span class=k2b-calendar-band__grip data-edge=start aria-hidden=true>`),
19
+ _tmpl$6 = /*#__PURE__*/_$template(`<span class=k2b-calendar-band__grip data-edge=end aria-hidden=true>`),
20
+ _tmpl$7 = /*#__PURE__*/_$template(`<div class=k2b-calendar-band><!$><!/><!$><!/><!$><!/><!$><!/>`),
21
+ _tmpl$8 = /*#__PURE__*/_$template(`<div class=k2b-calendar-marker><span class=k2b-calendar-marker__line aria-hidden=true></span><span class=k2b-calendar-marker__pill><!$><!/><!$><!/>`),
22
+ _tmpl$9 = /*#__PURE__*/_$template(`<span class=k2b-calendar-marker__time>`),
23
+ _tmpl$0 = /*#__PURE__*/_$template(`<button type=button class=k2b-calendar-gap__button><i class="ti ti-plus"aria-hidden=true></i><span class=k2b-calendar-gap__text>`),
24
+ _tmpl$1 = /*#__PURE__*/_$template(`<div class=k2b-calendar-gap><span class=k2b-calendar-gap__zone aria-hidden=true></span><!$><!/>`),
25
+ _tmpl$10 = /*#__PURE__*/_$template(`<span class=k2b-calendar-slot aria-hidden=true>`),
26
+ _tmpl$11 = /*#__PURE__*/_$template(`<div class=k2b-calendar-draft><span class=k2b-calendar-draft__grip data-edge=start aria-hidden=true></span><button type=button class=k2b-calendar-draft__open><i class="ti ti-plus"aria-hidden=true></i><span><!$><!/>–<!$><!/> · <!$><!/></span></button><span class=k2b-calendar-draft__grip data-edge=end aria-hidden=true>`),
27
+ _tmpl$12 = /*#__PURE__*/_$template(`<div class=k2b-calendar-ghost data-live=true aria-hidden=true><span class=k2b-calendar-ghost__title></span><span class=k2b-calendar-ghost__time></span><!$><!/>`),
28
+ _tmpl$13 = /*#__PURE__*/_$template(`<span class=k2b-calendar-ghost__fit>`),
29
+ _tmpl$14 = /*#__PURE__*/_$template(`<div class=k2b-calendar-popover popover=auto role=dialog data-kind=create>`),
30
+ _tmpl$15 = /*#__PURE__*/_$template(`<span class=k2b-calendar-allday-chip><!$><!/><!$><!/>`);
31
+ import { dates as calendar } from "@k2b/stdlib";
32
+ import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show, untrack } from "solid-js";
33
+ import { announce } from "../feedback/announce";
34
+ import { addDayKeys, buildDays, clockAt, DAY_MINUTES, dayKeyOf, dayLanes, dayMinuteAt, dayTopTerms, dayYTerms, freeGaps, GAP_LABEL_MINUTES, GAP_LONG_MINUTES, gapProposal, instantInDay, instantsInDay, minuteInDay, readDayGeometry, resizedInDay, snapMinutes, subtractTerms, termsCss, wakeMinutes } from "./calendar-days";
35
+ import { useCalendarMessages } from "./calendar-messages";
36
+ import { startCalendarPointerSession } from "./calendar-pointer";
37
+ import { EventChip, eventHref, ownerDateConfig, parseDate, placeCalendarPopover, validDate } from "./calendar-shared";
38
+
39
+ /**
40
+ * Shared state and parts of the day and week views with `dayHours`: the time model of the loaded days, the selection,
41
+ * the quick create, pointer gestures, and the pieces both views draw into a day's timeline.
42
+ */
43
+
44
+ /** What the header and the calendar's controller ask of the active day or week view. */
45
+
46
+ const COLOR_TOKENS = {
47
+ blue: "#3b82f6",
48
+ emerald: "#10b981",
49
+ amber: "#f59e0b",
50
+ red: "#ef4444",
51
+ violet: "#8b5cf6",
52
+ cyan: "#06b6d4",
53
+ zinc: "#a1a1aa"
54
+ };
55
+ export const eventAccent = event => event.colorHex ?? COLOR_TOKENS[event.color ?? "blue"];
56
+
57
+ /** Minimum minutes an overlap counts for a band, about the height of its title. */
58
+ const LANE_MINUTES = 20;
59
+ export const reducedMotion = () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
60
+
61
+ /** Swallows the click a browser sends right after a gesture ends, never the next one. */
62
+ export const swallowNextClick = () => {
63
+ const stop = event => {
64
+ event.stopPropagation();
65
+ event.preventDefault();
66
+ };
67
+ window.addEventListener("click", stop, {
68
+ capture: true,
69
+ once: true
70
+ });
71
+ window.setTimeout(() => window.removeEventListener("click", stop, {
72
+ capture: true
73
+ }), 120);
74
+ };
75
+ export const createTimeView = props => {
76
+ const messages = useCalendarMessages();
77
+ const dateConfig = createMemo(() => ownerDateConfig(props.owner));
78
+ const hours = () => props.hours;
79
+ const wake = createMemo(() => wakeMinutes(props.hours));
80
+ const keyOf = date => calendar.formatDateKey(date, dateConfig());
81
+ const parseKey = key => calendar.parseCalendarDate(key, dateConfig());
82
+
83
+ /** The loaded days: the host's range, or the day or the week of the date. */
84
+ const range = createMemo(() => {
85
+ const context = dateConfig();
86
+ const loaded = props.owner.range;
87
+ if (loaded) {
88
+ const from = parseDate(loaded.from, context);
89
+ const to = parseDate(loaded.to, context);
90
+ if (validDate(from) && validDate(to)) {
91
+ const first = calendar.formatDateKey(from, context);
92
+ const last = calendar.formatDateKey(new Date(Math.max(from.getTime(), to.getTime() - 1)), context);
93
+ return {
94
+ first,
95
+ last
96
+ };
97
+ }
98
+ }
99
+ const key = calendar.formatDateKey(props.date, context);
100
+ if (props.view === "day") return {
101
+ first: key,
102
+ last: key
103
+ };
104
+ const start = calendar.formatDateKey(calendar.startOfWeek(props.date, context), context);
105
+ return {
106
+ first: start,
107
+ last: addDayKeys(start, 6)
108
+ };
109
+ }, undefined, {
110
+ equals: (left, right) => left.first === right.first && left.last === right.last
111
+ });
112
+ const anchorKey = createMemo(() => keyOf(props.date));
113
+
114
+ /** Checkbox states the reader just set, shown until the host's events agree or drop the entry. */
115
+ const [checks, setChecks] = createSignal({});
116
+ const [justChecked, setJustChecked] = createSignal(new Set());
117
+ createEffect(on(() => props.events, events => {
118
+ const current = untrack(checks);
119
+ const ids = Object.keys(current);
120
+ if (ids.length === 0) return;
121
+ const next = {
122
+ ...current
123
+ };
124
+ for (const id of ids) {
125
+ const event = events.find(candidate => candidate.id === id);
126
+ if (!event || event.checked === current[id]) delete next[id];
127
+ }
128
+ setChecks(next);
129
+ }, {
130
+ defer: true
131
+ }));
132
+ const checkedOf = event => event.checked === undefined ? undefined : checks()[event.id] ?? event.checked;
133
+ /** What Space on an entry's chip does: check its box, where it has one the reader may change. */
134
+ const checkOf = event => checkedOf(event) !== undefined && props.owner.onEventCheck ? () => toggleCheck(event) : undefined;
135
+ const forgetJustChecked = id => setJustChecked(value => {
136
+ if (!value.has(id)) return value;
137
+ const rest = new Set(value);
138
+ rest.delete(id);
139
+ return rest;
140
+ });
141
+ /** Today's day for a moment after its last open to-do was checked off, which its heading celebrates. */
142
+ const [cheered, setCheered] = createSignal(null);
143
+ let cheerTimer = 0;
144
+ // The server renders the view too, and has no window.
145
+ onCleanup(() => clearTimeout(cheerTimer));
146
+ /** The entries with a checkbox on a day: its deadlines, the to-dos planned into its hours, and the ones due all day. */
147
+ const dueOn = key => {
148
+ const model = dayOf(key);
149
+ return [...model.markers.map(marker => marker.item), ...model.bands.map(band => band.item), ...model.spans.filter(span => span.marker && span.firstKey === key).map(span => span.item)].filter(item => item.checked !== undefined);
150
+ };
151
+ const toggleCheck = event => {
152
+ const current = checkedOf(event);
153
+ if (current === undefined || !props.owner.onEventCheck) return;
154
+ const next = !current;
155
+ setChecks(value => ({
156
+ ...value,
157
+ [event.id]: next
158
+ }));
159
+ if (next) {
160
+ setJustChecked(value => new Set([...value, event.id]));
161
+ window.setTimeout(() => forgetJustChecked(event.id), 1100);
162
+ const today = nowKey();
163
+ const due = dueOn(today);
164
+ if (due.includes(event) && due.every(item => checkedOf(item))) {
165
+ // The heading shows it only for a moment, so a screen reader hears it once instead.
166
+ announce(messages().calendarAllDone);
167
+ setCheered(today);
168
+ window.clearTimeout(cheerTimer);
169
+ cheerTimer = window.setTimeout(() => setCheered(null), 2200);
170
+ }
171
+ } else setCheered(null);
172
+ // A change the host refuses puts the box back as it was.
173
+ void Promise.resolve(props.owner.onEventCheck(event, next)).catch(() => {
174
+ forgetJustChecked(event.id);
175
+ if (next) setCheered(null);
176
+ setChecks(value => {
177
+ if (value[event.id] !== next) return value;
178
+ const rest = {
179
+ ...value
180
+ };
181
+ delete rest[event.id];
182
+ return rest;
183
+ });
184
+ });
185
+ };
186
+ const entries = createMemo(() => {
187
+ const context = dateConfig();
188
+ return props.events.flatMap(event => {
189
+ const start = parseDate(event.start, context);
190
+ if (!validDate(start)) return [];
191
+ const marker = event.display === "marker";
192
+ const parsedEnd = event.end ? parseDate(event.end, context) : null;
193
+ let end = parsedEnd && validDate(parsedEnd) && parsedEnd > start ? parsedEnd : null;
194
+ if (!end) end = event.allDay ? calendar.addDays(calendar.startOfDay(start, context), 1, context) : marker ? start : new Date(start.getTime() + 3_600_000);
195
+ return [{
196
+ item: event,
197
+ start,
198
+ end,
199
+ allDay: Boolean(event.allDay),
200
+ marker
201
+ }];
202
+ });
203
+ });
204
+ const days = createMemo(() => buildDays(range().first, range().last, entries(), props.hours, dateConfig()));
205
+ const emptyModel = key => ({
206
+ key,
207
+ bands: [],
208
+ markers: [],
209
+ spans: [],
210
+ night: false,
211
+ empty: true
212
+ });
213
+ const dayOf = key => days().get(key) ?? emptyModel(key);
214
+ const sourceOf = createMemo(() => new Map(entries().map(entry => [entry.item, entry])));
215
+
216
+ /** The day that holds now: before the start hour still the day before. */
217
+ const nowKey = createMemo(() => dayKeyOf(props.now, props.hours, dateConfig()));
218
+ const nowMinute = createMemo(() => minuteInDay(nowKey(), props.now, props.hours, dateConfig()));
219
+ /** Now as a minute of a day: past days lie wholly before it, coming days after. */
220
+ const nowIn = key => key === nowKey() ? nowMinute() : key < nowKey() ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
221
+ const calendarToday = createMemo(() => keyOf(props.now));
222
+ const clock = minute => clockAt(minute, props.hours);
223
+ const duration = minutes => messages().calendarDuration({
224
+ minutes: Math.max(1, Math.round(minutes))
225
+ });
226
+ const instant = (key, minute) => instantInDay(key, minute, props.hours, dateConfig());
227
+ const slot = (key, start, end) => instantsInDay(key, start, end, props.hours, dateConfig());
228
+ const dayStartMinute = hour => Math.max(0, (hour - props.hours[0]) * 60);
229
+
230
+ /** A day's timed piece as the event chip reads it. */
231
+ const normalized = (event, key) => {
232
+ const source = sourceOf().get(event);
233
+ const start = source?.start ?? parseDate(event.start, dateConfig());
234
+ const end = source?.end ?? start;
235
+ return {
236
+ ...event,
237
+ startDate: start,
238
+ endDate: end,
239
+ sourceStartDate: start,
240
+ sourceEndDate: end,
241
+ dayKey: key
242
+ };
243
+ };
244
+ const [selection, setSelection] = createSignal(null);
245
+ const [touchDraft, setTouchDraft] = createSignal(null);
246
+ const [activeKey, setActiveKey] = createSignal(null);
247
+ const [ghost, setGhost] = createSignal(null);
248
+ const [movingId, setMovingId] = createSignal("");
249
+ const [dragging, setDragging] = createSignal("");
250
+ const [nightOverrides, setNightOverrides] = createSignal({});
251
+ const [quick, setQuick] = createSignal(null);
252
+ const rangeOfSelection = value => value.start === undefined || value.end === undefined ? {
253
+ start: parseKey(value.key),
254
+ end: calendar.addDays(parseKey(value.key), 1, dateConfig()),
255
+ allDay: true
256
+ } : {
257
+ ...slot(value.key, value.start, value.end),
258
+ allDay: false
259
+ };
260
+ createEffect(on(selection, value => props.owner.onSelectionChange?.(value ? rangeOfSelection(value) : null), {
261
+ defer: true
262
+ }));
263
+ onCleanup(() => {
264
+ if (untrack(selection)) props.owner.onSelectionChange?.(null);
265
+ });
266
+
267
+ // Body elements of the days on screen, for pointer positions.
268
+ const bodies = new Map();
269
+ const registerBody = (key, element, expanded) => {
270
+ bodies.set(key, {
271
+ element,
272
+ expanded
273
+ });
274
+ onCleanup(() => {
275
+ if (bodies.get(key)?.element === element) bodies.delete(key);
276
+ });
277
+ };
278
+ const bodyAt = (clientX, clientY) => {
279
+ for (const [key, body] of bodies) {
280
+ if (!body.element.isConnected) continue;
281
+ const rect = body.element.getBoundingClientRect();
282
+ if (clientX < rect.left || clientX >= rect.right || clientY < rect.top || clientY >= rect.bottom) continue;
283
+ const geometry = readDayGeometry(body.element, wake(), body.expanded());
284
+ return {
285
+ key,
286
+ element: body.element,
287
+ geometry,
288
+ minute: dayMinuteAt(geometry, clientY - rect.top)
289
+ };
290
+ }
291
+ return null;
292
+ };
293
+ const limitOf = key => bodies.get(key)?.expanded() ? DAY_MINUTES : wake();
294
+ const nightOpen = (key, model) => nightOverrides()[key] ?? model.night;
295
+ const setNight = (key, open) => setNightOverrides(value => ({
296
+ ...value,
297
+ [key]: open
298
+ }));
299
+
300
+ /** The gap under a minute while dragging, and whether `need` minutes fit it; the gap shows "fits" or how much is missing. */
301
+ const fitAt = (key, minute, need, span) => {
302
+ const gap = freeGaps(dayOf(key).bands, wake(), Number.NEGATIVE_INFINITY).find(candidate => minute >= candidate.start && minute < candidate.end);
303
+ if (!gap) return null;
304
+ const fits = gap.end - gap.start >= need;
305
+ if (fits && span && (span.start < gap.start || span.end > gap.end)) return null;
306
+ return {
307
+ gap,
308
+ fits,
309
+ text: fits ? messages().calendarFits : messages().calendarTooShort({
310
+ duration: duration(need - (gap.end - gap.start))
311
+ })
312
+ };
313
+ };
314
+ const [gapHit, setGapHit] = createSignal(null);
315
+ const overlapAt = (key, start, end, except) => dayOf(key).bands.find(band => band.item.id !== except && band.start < end && band.end > start)?.item ?? null;
316
+ const openQuickCreate = (key, start, end, anchor, create) => {
317
+ const range = rangeOfSelection({
318
+ key,
319
+ start,
320
+ end
321
+ });
322
+ setTouchDraft(null);
323
+ if (!props.owner.renderQuickCreate) {
324
+ props.owner.onSlotActivate?.(range);
325
+ return;
326
+ }
327
+ setSelection({
328
+ key,
329
+ start,
330
+ end
331
+ });
332
+ setQuick({
333
+ range,
334
+ anchor,
335
+ create,
336
+ key,
337
+ start,
338
+ end
339
+ });
340
+ };
341
+ const canCreate = () => Boolean(props.owner.renderQuickCreate || props.owner.onSlotActivate);
342
+
343
+ /** The time a new entry takes: the selection, today's next full hour while today shows, else 09:00 of the first day. */
344
+ const createTarget = visible => {
345
+ const current = selection();
346
+ if (current && current.start !== undefined && current.end !== undefined) return {
347
+ key: current.key,
348
+ start: current.start,
349
+ end: current.end
350
+ };
351
+ const nine = dayStartMinute(9);
352
+ const nextHour = key => {
353
+ const minute = Math.ceil((nowMinute() + 1) / 60) * 60;
354
+ return key === nowKey() ? minute <= wake() - 60 ? minute : null : nine;
355
+ };
356
+ let key;
357
+ let start;
358
+ if (current) {
359
+ key = current.key;
360
+ start = key === nowKey() ? nextHour(key) : nine;
361
+ } else if (nowKey() >= visible.first && nowKey() <= visible.last) {
362
+ key = nowKey();
363
+ start = nextHour(key);
364
+ } else {
365
+ key = visible.first;
366
+ start = nine;
367
+ }
368
+ if (start === null) {
369
+ key = addDayKeys(key, 1);
370
+ start = nine;
371
+ }
372
+ return {
373
+ key,
374
+ start,
375
+ end: start + 60
376
+ };
377
+ };
378
+
379
+ // Pointer gestures: moving and resizing bands and markers, drawing a new event, and dragging entries in.
380
+ let cancelGesture;
381
+ onCleanup(() => cancelGesture?.());
382
+ const step = event => event.shiftKey ? 5 : 15;
383
+ const shiftHeld = {
384
+ current: false
385
+ };
386
+ const trackShift = event => {
387
+ shiftHeld.current = event.shiftKey;
388
+ };
389
+ onMount(() => {
390
+ window.addEventListener("keydown", trackShift, true);
391
+ window.addEventListener("keyup", trackShift, true);
392
+ onCleanup(() => {
393
+ window.removeEventListener("keydown", trackShift, true);
394
+ window.removeEventListener("keyup", trackShift, true);
395
+ });
396
+ });
397
+ const endGesture = () => {
398
+ setGhost(null);
399
+ setGapHit(null);
400
+ setMovingId("");
401
+ setDragging("");
402
+ };
403
+ const startMove = (pointerEvent, event, key, start, end, scroller) => {
404
+ const marker = event.display === "marker";
405
+ if (!props.owner.onEventDrop) return;
406
+ const body = bodies.get(key);
407
+ if (!body) return;
408
+ const rect = body.element.getBoundingClientRect();
409
+ const geometry = readDayGeometry(body.element, wake(), body.expanded());
410
+ const grab = dayMinuteAt(geometry, pointerEvent.clientY - rect.top) - start;
411
+ const length = end - start;
412
+ cancelGesture?.();
413
+ cancelGesture = startCalendarPointerSession({
414
+ event: pointerEvent,
415
+ scrollContainer: scroller,
416
+ resolve: (clientX, clientY) => {
417
+ const hit = bodyAt(clientX, clientY);
418
+ if (!hit) return null;
419
+ const limit = limitOf(hit.key);
420
+ const from = Math.max(0, Math.min(limit - Math.max(length, 15), snapMinutes(hit.minute - grab, step({
421
+ shiftKey: shiftHeld.current
422
+ }))));
423
+ return {
424
+ key: hit.key,
425
+ start: from,
426
+ end: from + length
427
+ };
428
+ },
429
+ onActivate: () => {
430
+ setMovingId(event.id);
431
+ setDragging("move");
432
+ setSelection(null);
433
+ },
434
+ onPreview: next => {
435
+ const clash = marker ? null : overlapAt(next.key, next.start, next.end, event.id);
436
+ const fit = marker ? null : fitAt(next.key, next.start + (next.end - next.start) / 2, next.end - next.start, next);
437
+ setGapHit(fit ? {
438
+ key: next.key,
439
+ start: fit.gap.start,
440
+ fits: fit.fits,
441
+ text: fit.text
442
+ } : null);
443
+ const time = marker ? messages().calendarDue({
444
+ time: clock(next.start)
445
+ }) : `${clock(next.start)}–${clock(next.end)} · ${duration(next.end - next.start)}`;
446
+ setGhost({
447
+ key: next.key,
448
+ start: next.start,
449
+ end: marker ? next.start + 20 : next.end,
450
+ title: event.title,
451
+ time: clash ? `${time} · ${messages().calendarOverlaps({
452
+ title: clash.title
453
+ })}` : time,
454
+ colorHex: eventAccent(event),
455
+ warn: Boolean(clash),
456
+ fit: fit ? {
457
+ fits: fit.fits,
458
+ text: fit.text
459
+ } : undefined
460
+ });
461
+ },
462
+ onCommit: next => {
463
+ endGesture();
464
+ swallowNextClick();
465
+ if (next.key === key && next.start === start && next.end === end) return;
466
+ const source = sourceOf().get(event);
467
+ // A moved entry keeps its real length, also when it lands across a clock change.
468
+ const startDate = instant(next.key, next.start);
469
+ const endDate = marker ? startDate : source ? new Date(startDate.getTime() + source.end.getTime() - source.start.getTime()) : slot(next.key, next.start, next.end).end;
470
+ if (source && source.start.getTime() === startDate.getTime() && source.end.getTime() === endDate.getTime()) return;
471
+ props.owner.onEventDrop?.(event, {
472
+ start: startDate,
473
+ end: endDate,
474
+ allDay: false
475
+ });
476
+ },
477
+ onCancel: endGesture
478
+ });
479
+ };
480
+ const startResize = (pointerEvent, event, key, start, end, edge, scroller) => {
481
+ if (!props.owner.onEventResize) return;
482
+ pointerEvent.preventDefault();
483
+ pointerEvent.stopPropagation();
484
+ const body = bodies.get(key);
485
+ if (!body) return;
486
+ cancelGesture?.();
487
+ cancelGesture = startCalendarPointerSession({
488
+ event: pointerEvent,
489
+ scrollContainer: scroller,
490
+ threshold: 1,
491
+ resolve: (_clientX, clientY) => {
492
+ const rect = body.element.getBoundingClientRect();
493
+ const geometry = readDayGeometry(body.element, wake(), body.expanded());
494
+ const minute = snapMinutes(dayMinuteAt(geometry, clientY - rect.top), step({
495
+ shiftKey: shiftHeld.current
496
+ }));
497
+ const limit = limitOf(key);
498
+ const size = step({
499
+ shiftKey: shiftHeld.current
500
+ });
501
+ return edge === "end" ? {
502
+ start,
503
+ end: Math.max(start + size, Math.min(limit, minute))
504
+ } : {
505
+ start: Math.max(0, Math.min(end - size, minute)),
506
+ end
507
+ };
508
+ },
509
+ onActivate: () => {
510
+ setMovingId(event.id);
511
+ setDragging(edge);
512
+ },
513
+ onPreview: next => setGhost({
514
+ key,
515
+ start: next.start,
516
+ end: next.end,
517
+ title: event.title,
518
+ time: `${clock(next.start)}–${clock(next.end)} · ${duration(next.end - next.start)}`,
519
+ colorHex: eventAccent(event)
520
+ }),
521
+ onCommit: next => {
522
+ endGesture();
523
+ swallowNextClick();
524
+ if (next.start === start && next.end === end) return;
525
+ const source = sourceOf().get(event);
526
+ const range = source ? resizedInDay(source, edge, key, next, props.hours, dateConfig()) : slot(key, next.start, next.end);
527
+ props.owner.onEventResize?.(event, {
528
+ ...range,
529
+ allDay: false
530
+ });
531
+ },
532
+ onCancel: endGesture
533
+ });
534
+ };
535
+
536
+ /** Drawing on free time: a new event with its live length, on a 15-minute grid, with Shift on 5 minutes. */
537
+ const startDraw = (pointerEvent, key, scroller) => {
538
+ if (!canCreate() || pointerEvent.pointerType !== "mouse") return;
539
+ const body = bodies.get(key);
540
+ if (!body) return;
541
+ const minuteAt = clientY => {
542
+ const rect = body.element.getBoundingClientRect();
543
+ const geometry = readDayGeometry(body.element, wake(), body.expanded());
544
+ return Math.max(0, Math.min(limitOf(key), dayMinuteAt(geometry, clientY - rect.top)));
545
+ };
546
+ const origin = minuteAt(pointerEvent.clientY);
547
+ cancelGesture?.();
548
+ cancelGesture = startCalendarPointerSession({
549
+ event: pointerEvent,
550
+ scrollContainer: scroller,
551
+ resolve: (_clientX, clientY) => {
552
+ const size = step({
553
+ shiftKey: shiftHeld.current
554
+ });
555
+ const minute = minuteAt(clientY);
556
+ const from = Math.floor(Math.min(origin, minute) / size) * size;
557
+ let to = Math.ceil(Math.max(origin, minute) / size) * size;
558
+ if (to - from < size) to = from + size;
559
+ return {
560
+ start: from,
561
+ end: Math.min(to, limitOf(key))
562
+ };
563
+ },
564
+ onActivate: () => {
565
+ setDragging("draw");
566
+ setSelection(null);
567
+ setTouchDraft(null);
568
+ },
569
+ onPreview: next => setGhost({
570
+ key,
571
+ start: next.start,
572
+ end: next.end,
573
+ title: messages().calendarNewEvent,
574
+ time: `${clock(next.start)}–${clock(next.end)} · ${duration(next.end - next.start)}`
575
+ }),
576
+ onCommit: next => {
577
+ const anchor = document.querySelector(".k2b-calendar-ghost[data-live]")?.getBoundingClientRect() ?? body.element;
578
+ endGesture();
579
+ swallowNextClick();
580
+ openQuickCreate(key, next.start, next.end, anchor);
581
+ },
582
+ onCancel: endGesture
583
+ });
584
+ };
585
+
586
+ /** Follows a pointer that pressed an entry outside the calendar into its free time. */
587
+ const dragIn = (pointerEvent, item, onDrop, scroller) => {
588
+ if (pointerEvent.button !== 0 || !pointerEvent.isPrimary) return;
589
+ cancelGesture?.();
590
+ cancelGesture = undefined;
591
+ const length = Math.max(15, Math.round(item.minutes));
592
+ const color = item.colorHex ?? COLOR_TOKENS[item.color ?? "blue"];
593
+ let token;
594
+ let spring;
595
+ const target = pointerEvent.target instanceof HTMLElement ? pointerEvent.target.closest("li, [data-calendar-drag-source]") : null;
596
+ let held = false;
597
+ let cleaned = false;
598
+ const preventContextMenu = event => event.preventDefault();
599
+ const preventScroll = event => {
600
+ if (held) event.preventDefault();
601
+ };
602
+ const cleanup = () => {
603
+ if (cleaned) return;
604
+ cleaned = true;
605
+ document.removeEventListener("touchmove", preventScroll);
606
+ window.removeEventListener("contextmenu", preventContextMenu, true);
607
+ if (pointerEvent.pointerType === "touch") held = false;
608
+ if (target) delete target.dataset.lifted;
609
+ token?.remove();
610
+ token = undefined;
611
+ if (spring) window.clearTimeout(spring.timer);
612
+ spring = undefined;
613
+ endGesture();
614
+ };
615
+ const begin = () => {
616
+ cancelGesture = startCalendarPointerSession({
617
+ event: pointerEvent,
618
+ scrollContainer: scroller,
619
+ armAutoScroll: true,
620
+ threshold: held && pointerEvent.pointerType !== "mouse" ? 1 : 6,
621
+ resolve: (clientX, clientY) => {
622
+ const hit = bodyAt(clientX, clientY);
623
+ if (!hit || hit.minute >= limitOf(hit.key)) return {
624
+ key: "",
625
+ start: 0,
626
+ end: 0,
627
+ clash: null
628
+ };
629
+ const fit = fitAt(hit.key, hit.minute, length);
630
+ let start = snapMinutes(hit.minute - length / 2, 15);
631
+ if (fit?.fits) start = Math.max(fit.gap.start, Math.min(fit.gap.end - length, start));
632
+ start = Math.max(0, Math.min(limitOf(hit.key) - length, start));
633
+ return {
634
+ key: hit.key,
635
+ start,
636
+ end: start + length,
637
+ clash: overlapAt(hit.key, start, start + length),
638
+ fit
639
+ };
640
+ },
641
+ onActivate: () => {
642
+ setDragging("external");
643
+ setSelection(null);
644
+ token = document.createElement("div");
645
+ token.className = "k2b-calendar-token";
646
+ token.textContent = item.title;
647
+ token.style.setProperty("--k2b-calendar-accent", color);
648
+ token.setAttribute("aria-hidden", "true");
649
+ document.body.append(token);
650
+ },
651
+ onMove: (clientX, clientY) => {
652
+ if (token) token.style.transform = `translate(${clientX + 12}px, ${clientY + 10}px)`;
653
+ // A folded run of empty days opens when the entry rests on it.
654
+ const empty = document.elementFromPoint(clientX, clientY)?.closest("[data-calendar-unfold]");
655
+ if (empty && spring?.element !== empty) {
656
+ if (spring) window.clearTimeout(spring.timer);
657
+ spring = {
658
+ element: empty,
659
+ timer: window.setTimeout(() => empty.click(), 650)
660
+ };
661
+ } else if (!empty && spring) {
662
+ window.clearTimeout(spring.timer);
663
+ spring = undefined;
664
+ }
665
+ },
666
+ onPreview: next => {
667
+ if (token) token.dataset.hidden = next.key ? "true" : "false";
668
+ if (!next.key) {
669
+ setGhost(null);
670
+ setGapHit(null);
671
+ return;
672
+ }
673
+ setGapHit(next.fit ? {
674
+ key: next.key,
675
+ start: next.fit.gap.start,
676
+ fits: next.fit.fits,
677
+ text: next.fit.text
678
+ } : null);
679
+ const time = `${clock(next.start)}–${clock(next.end)} · ${duration(length)}`;
680
+ setGhost({
681
+ key: next.key,
682
+ start: next.start,
683
+ end: next.end,
684
+ title: item.title,
685
+ time: next.clash ? `${time} · ${messages().calendarOverlaps({
686
+ title: next.clash.title
687
+ })}` : time,
688
+ colorHex: color,
689
+ warn: Boolean(next.clash),
690
+ fit: next.fit ? {
691
+ fits: next.fit.fits,
692
+ text: next.fit.text
693
+ } : undefined
694
+ });
695
+ },
696
+ onCommit: next => {
697
+ swallowNextClick();
698
+ if (next.key) onDrop({
699
+ ...slot(next.key, next.start, next.end),
700
+ allDay: false
701
+ });
702
+ },
703
+ onEnd: cleanup
704
+ });
705
+ };
706
+ if (pointerEvent.pointerType === "mouse") {
707
+ begin();
708
+ return;
709
+ }
710
+ // A finger first holds the entry still, so a short swipe keeps scrolling the list it sits in.
711
+ const origin = {
712
+ x: pointerEvent.clientX,
713
+ y: pointerEvent.clientY
714
+ };
715
+ const abort = () => {
716
+ window.clearTimeout(timer);
717
+ window.removeEventListener("pointermove", watch);
718
+ window.removeEventListener("pointerup", abort);
719
+ window.removeEventListener("pointercancel", abort);
720
+ window.removeEventListener("contextmenu", preventContextMenu, true);
721
+ if (target) delete target.dataset.lifted;
722
+ if (cancelGesture === abort) cancelGesture = undefined;
723
+ };
724
+ const watch = event => {
725
+ if (event.pointerId === pointerEvent.pointerId && Math.hypot(event.clientX - origin.x, event.clientY - origin.y) > 10) abort();
726
+ };
727
+ const timer = window.setTimeout(() => {
728
+ abort();
729
+ held = true;
730
+ if (target) target.dataset.lifted = "true";
731
+ navigator.vibrate?.(8);
732
+ document.addEventListener("touchmove", preventScroll, {
733
+ passive: false
734
+ });
735
+ window.addEventListener("contextmenu", preventContextMenu, true);
736
+ begin();
737
+ }, 420);
738
+ window.addEventListener("pointermove", watch);
739
+ window.addEventListener("pointerup", abort);
740
+ window.addEventListener("pointercancel", abort);
741
+ window.addEventListener("contextmenu", preventContextMenu, true);
742
+ cancelGesture = abort;
743
+ };
744
+ return {
745
+ props,
746
+ messages,
747
+ dateConfig,
748
+ hours,
749
+ wake,
750
+ range,
751
+ anchorKey,
752
+ days,
753
+ dayOf,
754
+ keyOf,
755
+ parseKey,
756
+ nowKey,
757
+ nowMinute,
758
+ nowIn,
759
+ calendarToday,
760
+ clock,
761
+ duration,
762
+ instant,
763
+ normalized,
764
+ checkedOf,
765
+ toggleCheck,
766
+ checkOf,
767
+ justChecked,
768
+ cheered,
769
+ selection,
770
+ setSelection,
771
+ touchDraft,
772
+ setTouchDraft,
773
+ activeKey,
774
+ setActiveKey,
775
+ ghost,
776
+ gapHit,
777
+ movingId,
778
+ dragging,
779
+ nightOpen,
780
+ setNight,
781
+ quick,
782
+ setQuick,
783
+ openQuickCreate,
784
+ canCreate,
785
+ createTarget,
786
+ rangeOfSelection,
787
+ registerBody,
788
+ bodyAt,
789
+ limitOf,
790
+ startMove,
791
+ startResize,
792
+ startDraw,
793
+ dragIn,
794
+ dayStartMinute
795
+ };
796
+ };
797
+ /**
798
+ * German short dates read "Fr 9. Okt", without the dots of the abbreviations; with `comma`, a title such as
799
+ * "Fr, 9. Okt 2026" keeps the comma after the day.
800
+ */
801
+ export const compactGermanDate = (text, locale, comma = false) => locale?.startsWith("de") ? text.replace(comma ? /\b(Mo|Di|Mi|Do|Fr|Sa|So)\./g : /\b(Mo|Di|Mi|Do|Fr|Sa|So)\.,?/g, "$1").replace(/\b(Jan|Feb|Mär|Apr|Jun|Jul|Aug|Sep|Sept|Okt|Nov|Dez)\./g, "$1") : text;
802
+
803
+ /** A short day such as "Fri 9" or "Fr 9.", in the calendar's locale. */
804
+ export const shortDay = (view, key, withMonth = false) => compactGermanDate(view.parseKey(key).toLocaleDateString(view.dateConfig().locale ?? "en", {
805
+ weekday: "short",
806
+ day: "numeric",
807
+ ...(withMonth ? {
808
+ month: "short"
809
+ } : {}),
810
+ timeZone: view.dateConfig().timeZone
811
+ }), view.dateConfig().locale);
812
+
813
+ /** A long day such as "Friday, October 9", with the year when it is not this year's. */
814
+ export const longDay = (view, key) => view.parseKey(key).toLocaleDateString(view.dateConfig().locale ?? "en", {
815
+ weekday: "long",
816
+ month: "long",
817
+ day: "numeric",
818
+ ...(key.slice(0, 4) !== view.calendarToday().slice(0, 4) ? {
819
+ year: "numeric"
820
+ } : {}),
821
+ timeZone: view.dateConfig().timeZone
822
+ });
823
+ const CHECK_PATH = "M4.6 8.3l2.3 2.3 4.6-4.9";
824
+
825
+ /** A to-do's checkbox: it draws its check, and the title beside it strikes through. */
826
+ export const TimeCheck = props => {
827
+ const checked = () => props.view.checkedOf(props.event) ?? false;
828
+ const box = (() => {
829
+ var _el$ = _$getNextElement(_tmpl$),
830
+ _el$2 = _el$.firstChild,
831
+ _el$3 = _el$2.nextSibling;
832
+ return _el$;
833
+ })();
834
+ return _$createComponent(Show, {
835
+ get when() {
836
+ return props.view.props.owner.onEventCheck;
837
+ },
838
+ get fallback() {
839
+ return (() => {
840
+ var _el$5 = _$getNextElement(_tmpl$3);
841
+ _$insert(_el$5, box);
842
+ _$effect(() => _$setAttribute(_el$5, "data-checked", checked() ? "true" : undefined));
843
+ return _el$5;
844
+ })();
845
+ },
846
+ get children() {
847
+ var _el$4 = _$getNextElement(_tmpl$2);
848
+ _el$4.$$click = event => {
849
+ event.preventDefault();
850
+ event.stopPropagation();
851
+ props.view.toggleCheck(props.event);
852
+ };
853
+ _el$4.$$pointerdown = event => event.stopPropagation();
854
+ _$insert(_el$4, box);
855
+ _$effect(_p$ => {
856
+ var _v$ = checked() ? "true" : "false",
857
+ _v$2 = props.view.messages().calendarDone({
858
+ title: props.event.title
859
+ }),
860
+ _v$3 = props.tabIndex,
861
+ _v$4 = checked() ? "true" : undefined,
862
+ _v$5 = props.view.justChecked().has(props.event.id) ? "true" : undefined;
863
+ _v$ !== _p$.e && _$setAttribute(_el$4, "aria-checked", _p$.e = _v$);
864
+ _v$2 !== _p$.t && _$setAttribute(_el$4, "aria-label", _p$.t = _v$2);
865
+ _v$3 !== _p$.a && _$setAttribute(_el$4, "tabindex", _p$.a = _v$3);
866
+ _v$4 !== _p$.o && _$setAttribute(_el$4, "data-checked", _p$.o = _v$4);
867
+ _v$5 !== _p$.i && _$setAttribute(_el$4, "data-just-checked", _p$.i = _v$5);
868
+ return _p$;
869
+ }, {
870
+ e: undefined,
871
+ t: undefined,
872
+ a: undefined,
873
+ o: undefined,
874
+ i: undefined
875
+ });
876
+ _$runHydrationEvents();
877
+ return _el$4;
878
+ }
879
+ });
880
+ };
881
+
882
+ /** A timed entry in a day's timeline: a band as long as the entry, side by side with overlapping ones. */
883
+ export const TimeBand = props => {
884
+ const view = props.view;
885
+ const event = () => props.band.item;
886
+ const wake = () => view.wake();
887
+ const limit = () => props.expanded ? DAY_MINUTES : wake();
888
+ const visibleStart = () => Math.max(0, props.band.start);
889
+ const visibleEnd = () => Math.min(limit(), props.band.end);
890
+ const top = () => dayTopTerms(wake(), props.expanded, visibleStart());
891
+ const bottom = () => dayYTerms(wake(), props.expanded, visibleEnd());
892
+ const now = () => view.nowIn(props.dayKey);
893
+ const running = () => now() >= props.band.start && now() < props.band.end && Number.isFinite(now());
894
+ const past = () => now() >= props.band.end;
895
+ const length = () => props.band.end - props.band.start;
896
+ const pixels = () => length() / 60 * props.hourPx - 2;
897
+ const size = () => pixels() < 27 ? "xs" : pixels() < 40 ? "s" : "m";
898
+ const checked = () => view.checkedOf(event());
899
+ const remaining = () => running() ? view.messages().calendarRemaining({
900
+ duration: view.duration(props.band.end - now())
901
+ }) : undefined;
902
+ const lines = () => Math.max(1, Math.floor((pixels() - 24) / 15));
903
+ return (() => {
904
+ var _el$6 = _$getNextElement(_tmpl$7),
905
+ _el$0 = _el$6.firstChild,
906
+ [_el$1, _co$] = _$getNextMarker(_el$0.nextSibling),
907
+ _el$10 = _el$1.nextSibling,
908
+ [_el$11, _co$2] = _$getNextMarker(_el$10.nextSibling),
909
+ _el$12 = _el$11.nextSibling,
910
+ [_el$13, _co$3] = _$getNextMarker(_el$12.nextSibling),
911
+ _el$14 = _el$13.nextSibling,
912
+ [_el$15, _co$4] = _$getNextMarker(_el$14.nextSibling);
913
+ _$insert(_el$6, _$createComponent(Show, {
914
+ get when() {
915
+ return running();
916
+ },
917
+ get children() {
918
+ return _$getNextElement(_tmpl$4);
919
+ }
920
+ }), _el$1, _co$);
921
+ _$insert(_el$6, _$createComponent(Show, {
922
+ get when() {
923
+ return checked() !== undefined;
924
+ },
925
+ get children() {
926
+ return _$createComponent(TimeCheck, {
927
+ view: view,
928
+ get event() {
929
+ return event();
930
+ },
931
+ get tabIndex() {
932
+ return props.tabIndex === undefined ? undefined : -1;
933
+ }
934
+ });
935
+ }
936
+ }), _el$11, _co$2);
937
+ _$insert(_el$6, _$createComponent(EventChip, {
938
+ get event() {
939
+ return view.normalized(event(), props.dayKey);
940
+ },
941
+ get owner() {
942
+ return view.props.owner;
943
+ },
944
+ get href() {
945
+ return eventHref(view.props.owner, event());
946
+ },
947
+ fill: true,
948
+ timeline: true,
949
+ get compact() {
950
+ return props.compact;
951
+ },
952
+ get timeLabel() {
953
+ return `${view.clock(props.band.start)}–${view.clock(props.band.end)}`;
954
+ },
955
+ get showLocation() {
956
+ return _$memo(() => !!(props.showLocation && size() === "m"))() && pixels() >= 64;
957
+ },
958
+ get remaining() {
959
+ return remaining();
960
+ },
961
+ get checkbox() {
962
+ return checked() !== undefined;
963
+ },
964
+ get onCheck() {
965
+ return view.checkOf(event());
966
+ },
967
+ get moving() {
968
+ return view.movingId() === event().id;
969
+ },
970
+ get tabIndex() {
971
+ return props.tabIndex;
972
+ },
973
+ get stop() {
974
+ return props.stop;
975
+ },
976
+ get onMovePointerDown() {
977
+ return view.props.owner.onEventDrop ? pointerEvent => view.startMove(pointerEvent, event(), props.dayKey, props.band.start, props.band.end, props.scroller?.()) : undefined;
978
+ }
979
+ }), _el$13, _co$3);
980
+ _$insert(_el$6, _$createComponent(Show, {
981
+ get when() {
982
+ return view.props.owner.onEventResize;
983
+ },
984
+ get children() {
985
+ return [(() => {
986
+ var _el$8 = _$getNextElement(_tmpl$5);
987
+ _el$8.$$pointerdown = pointerEvent => view.startResize(pointerEvent, event(), props.dayKey, props.band.start, props.band.end, "start", props.scroller?.());
988
+ _$runHydrationEvents();
989
+ return _el$8;
990
+ })(), _$createComponent(Show, {
991
+ get when() {
992
+ return props.band.end <= limit();
993
+ },
994
+ get children() {
995
+ var _el$9 = _$getNextElement(_tmpl$6);
996
+ _el$9.$$pointerdown = pointerEvent => view.startResize(pointerEvent, event(), props.dayKey, props.band.start, props.band.end, "end", props.scroller?.());
997
+ _$runHydrationEvents();
998
+ return _el$9;
999
+ }
1000
+ })];
1001
+ }
1002
+ }), _el$15, _co$4);
1003
+ _$effect(_p$ => {
1004
+ var _v$6 = event().id,
1005
+ _v$7 = size(),
1006
+ _v$8 = past() ? "true" : undefined,
1007
+ _v$9 = running() ? "true" : undefined,
1008
+ _v$0 = checked() ? "true" : undefined,
1009
+ _v$1 = checked() !== undefined ? "true" : undefined,
1010
+ _v$10 = view.justChecked().has(event().id) ? "true" : undefined,
1011
+ _v$11 = props.band.end > limit() ? "true" : undefined,
1012
+ _v$12 = view.movingId() === event().id ? "true" : undefined,
1013
+ _v$13 = event().display,
1014
+ _v$14 = eventAccent(event()),
1015
+ _v$15 = String(props.band.lane),
1016
+ _v$16 = String(props.band.lanes),
1017
+ _v$17 = String(lines()),
1018
+ _v$18 = running() ? `${((now() - props.band.start) / length() * 100).toFixed(1)}%` : undefined,
1019
+ _v$19 = `calc(${termsCss(top())} + 1px)`,
1020
+ _v$20 = `max(var(--k2b-calendar-band-min), calc(${termsCss(subtractTerms(bottom(), top()))} - 2px))`;
1021
+ _v$6 !== _p$.e && _$setAttribute(_el$6, "data-calendar-entry", _p$.e = _v$6);
1022
+ _v$7 !== _p$.t && _$setAttribute(_el$6, "data-size", _p$.t = _v$7);
1023
+ _v$8 !== _p$.a && _$setAttribute(_el$6, "data-past", _p$.a = _v$8);
1024
+ _v$9 !== _p$.o && _$setAttribute(_el$6, "data-running", _p$.o = _v$9);
1025
+ _v$0 !== _p$.i && _$setAttribute(_el$6, "data-checked", _p$.i = _v$0);
1026
+ _v$1 !== _p$.n && _$setAttribute(_el$6, "data-checkbox", _p$.n = _v$1);
1027
+ _v$10 !== _p$.s && _$setAttribute(_el$6, "data-just-checked", _p$.s = _v$10);
1028
+ _v$11 !== _p$.h && _$setAttribute(_el$6, "data-cut-bottom", _p$.h = _v$11);
1029
+ _v$12 !== _p$.r && _$setAttribute(_el$6, "data-moving", _p$.r = _v$12);
1030
+ _v$13 !== _p$.d && _$setAttribute(_el$6, "data-display", _p$.d = _v$13);
1031
+ _v$14 !== _p$.l && _$setStyleProperty(_el$6, "--k2b-calendar-accent", _p$.l = _v$14);
1032
+ _v$15 !== _p$.u && _$setStyleProperty(_el$6, "--k2b-calendar-lane", _p$.u = _v$15);
1033
+ _v$16 !== _p$.c && _$setStyleProperty(_el$6, "--k2b-calendar-lanes", _p$.c = _v$16);
1034
+ _v$17 !== _p$.w && _$setStyleProperty(_el$6, "--k2b-calendar-lines", _p$.w = _v$17);
1035
+ _v$18 !== _p$.m && _$setStyleProperty(_el$6, "--k2b-calendar-progress", _p$.m = _v$18);
1036
+ _v$19 !== _p$.f && _$setStyleProperty(_el$6, "top", _p$.f = _v$19);
1037
+ _v$20 !== _p$.y && _$setStyleProperty(_el$6, "height", _p$.y = _v$20);
1038
+ return _p$;
1039
+ }, {
1040
+ e: undefined,
1041
+ t: undefined,
1042
+ a: undefined,
1043
+ o: undefined,
1044
+ i: undefined,
1045
+ n: undefined,
1046
+ s: undefined,
1047
+ h: undefined,
1048
+ r: undefined,
1049
+ d: undefined,
1050
+ l: undefined,
1051
+ u: undefined,
1052
+ c: undefined,
1053
+ w: undefined,
1054
+ m: undefined,
1055
+ f: undefined,
1056
+ y: undefined
1057
+ });
1058
+ return _el$6;
1059
+ })();
1060
+ };
1061
+
1062
+ /** A point in time such as a deadline: a dashed line across the day and a pill with its checkbox and title. */
1063
+ export const TimeMarker = props => {
1064
+ const view = props.view;
1065
+ const checked = () => view.checkedOf(props.event);
1066
+ const past = () => view.nowIn(props.dayKey) >= props.minute;
1067
+ return (() => {
1068
+ var _el$16 = _$getNextElement(_tmpl$8),
1069
+ _el$17 = _el$16.firstChild,
1070
+ _el$18 = _el$17.nextSibling,
1071
+ _el$19 = _el$18.firstChild,
1072
+ [_el$20, _co$5] = _$getNextMarker(_el$19.nextSibling),
1073
+ _el$21 = _el$20.nextSibling,
1074
+ [_el$22, _co$6] = _$getNextMarker(_el$21.nextSibling);
1075
+ _$insert(_el$18, _$createComponent(Show, {
1076
+ get when() {
1077
+ return checked() !== undefined;
1078
+ },
1079
+ get children() {
1080
+ return _$createComponent(TimeCheck, {
1081
+ view: view,
1082
+ get event() {
1083
+ return props.event;
1084
+ },
1085
+ get tabIndex() {
1086
+ return props.tabIndex === undefined ? undefined : -1;
1087
+ }
1088
+ });
1089
+ }
1090
+ }), _el$20, _co$5);
1091
+ _$insert(_el$18, _$createComponent(EventChip, {
1092
+ get event() {
1093
+ return view.normalized(props.event, props.dayKey);
1094
+ },
1095
+ get owner() {
1096
+ return view.props.owner;
1097
+ },
1098
+ get href() {
1099
+ return eventHref(view.props.owner, props.event);
1100
+ },
1101
+ compact: true,
1102
+ timeline: true,
1103
+ get checkbox() {
1104
+ return checked() !== undefined;
1105
+ },
1106
+ get onCheck() {
1107
+ return view.checkOf(props.event);
1108
+ },
1109
+ get tabIndex() {
1110
+ return props.tabIndex;
1111
+ },
1112
+ get stop() {
1113
+ return props.stop;
1114
+ },
1115
+ get trailing() {
1116
+ return _$createComponent(Show, {
1117
+ get when() {
1118
+ return props.showTime;
1119
+ },
1120
+ get children() {
1121
+ var _el$23 = _$getNextElement(_tmpl$9);
1122
+ _$insert(_el$23, () => view.clock(props.minute));
1123
+ return _el$23;
1124
+ }
1125
+ });
1126
+ },
1127
+ get onMovePointerDown() {
1128
+ return view.props.owner.onEventDrop ? pointerEvent => view.startMove(pointerEvent, props.event, props.dayKey, props.minute, props.minute, props.scroller?.()) : undefined;
1129
+ }
1130
+ }), _el$22, _co$6);
1131
+ _$effect(_p$ => {
1132
+ var _v$21 = props.event.id,
1133
+ _v$22 = past() ? "true" : undefined,
1134
+ _v$23 = past() && checked() === false ? "true" : undefined,
1135
+ _v$24 = checked() ? "true" : undefined,
1136
+ _v$25 = view.justChecked().has(props.event.id) ? "true" : undefined,
1137
+ _v$26 = props.above ? "true" : undefined,
1138
+ _v$27 = view.movingId() === props.event.id ? "true" : undefined,
1139
+ _v$28 = eventAccent(props.event),
1140
+ _v$29 = String(props.lane),
1141
+ _v$30 = String(props.lanes),
1142
+ _v$31 = termsCss(dayTopTerms(view.wake(), props.expanded, props.minute));
1143
+ _v$21 !== _p$.e && _$setAttribute(_el$16, "data-calendar-entry", _p$.e = _v$21);
1144
+ _v$22 !== _p$.t && _$setAttribute(_el$16, "data-past", _p$.t = _v$22);
1145
+ _v$23 !== _p$.a && _$setAttribute(_el$16, "data-overdue", _p$.a = _v$23);
1146
+ _v$24 !== _p$.o && _$setAttribute(_el$16, "data-checked", _p$.o = _v$24);
1147
+ _v$25 !== _p$.i && _$setAttribute(_el$16, "data-just-checked", _p$.i = _v$25);
1148
+ _v$26 !== _p$.n && _$setAttribute(_el$16, "data-above", _p$.n = _v$26);
1149
+ _v$27 !== _p$.s && _$setAttribute(_el$16, "data-moving", _p$.s = _v$27);
1150
+ _v$28 !== _p$.h && _$setStyleProperty(_el$16, "--k2b-calendar-accent", _p$.h = _v$28);
1151
+ _v$29 !== _p$.r && _$setStyleProperty(_el$16, "--k2b-calendar-marker-lane", _p$.r = _v$29);
1152
+ _v$30 !== _p$.d && _$setStyleProperty(_el$16, "--k2b-calendar-marker-lanes", _p$.d = _v$30);
1153
+ _v$31 !== _p$.l && _$setStyleProperty(_el$16, "top", _p$.l = _v$31);
1154
+ return _p$;
1155
+ }, {
1156
+ e: undefined,
1157
+ t: undefined,
1158
+ a: undefined,
1159
+ o: undefined,
1160
+ i: undefined,
1161
+ n: undefined,
1162
+ s: undefined,
1163
+ h: undefined,
1164
+ r: undefined,
1165
+ d: undefined,
1166
+ l: undefined
1167
+ });
1168
+ return _el$16;
1169
+ })();
1170
+ };
1171
+
1172
+ /** Free time between entries, labelled from an hour on; activating it creates there. */
1173
+ export const TimeGap = props => {
1174
+ const view = props.view;
1175
+ const length = () => props.gap.end - props.gap.start;
1176
+ const proposal = () => gapProposal(props.gap, view.hours());
1177
+ const label = () => view.messages().calendarFree({
1178
+ duration: view.duration(length())
1179
+ });
1180
+ const hit = () => {
1181
+ const current = view.gapHit();
1182
+ return current && current.key === props.dayKey && current.start === props.gap.start ? current : null;
1183
+ };
1184
+ const top = () => dayYTerms(view.wake(), props.expanded, props.gap.start);
1185
+ /**
1186
+ * Shorter free time is hidden until something is dragged into it, and past free time has no label, so neither is a
1187
+ * keyboard stop.
1188
+ */
1189
+ const labelled = () => length() >= GAP_LABEL_MINUTES;
1190
+ const stop = () => labelled() && !props.gap.past ? props.stop : undefined;
1191
+ return (() => {
1192
+ var _el$24 = _$getNextElement(_tmpl$1),
1193
+ _el$25 = _el$24.firstChild,
1194
+ _el$29 = _el$25.nextSibling,
1195
+ [_el$30, _co$7] = _$getNextMarker(_el$29.nextSibling);
1196
+ _$insert(_el$24, _$createComponent(Show, {
1197
+ get when() {
1198
+ return view.canCreate();
1199
+ },
1200
+ get children() {
1201
+ var _el$26 = _$getNextElement(_tmpl$0),
1202
+ _el$27 = _el$26.firstChild,
1203
+ _el$28 = _el$27.nextSibling;
1204
+ _el$26.$$click = event => {
1205
+ event.stopPropagation();
1206
+ view.openQuickCreate(props.dayKey, proposal()[0], proposal()[1], event.currentTarget);
1207
+ };
1208
+ _$insert(_el$28, (() => {
1209
+ var _c$ = _$memo(() => !!hit());
1210
+ return () => _c$() ? hit().text : label();
1211
+ })());
1212
+ _$effect(_p$ => {
1213
+ var _v$32 = labelled() ? props.tabIndex ?? (props.gap.past ? -1 : 0) : -1,
1214
+ _v$33 = stop()?.id,
1215
+ _v$34 = stop()?.column,
1216
+ _v$35 = stop()?.minute,
1217
+ _v$36 = view.messages().calendarFreeAction({
1218
+ duration: view.duration(length()),
1219
+ from: view.clock(props.gap.start),
1220
+ to: view.clock(props.gap.end),
1221
+ start: view.clock(proposal()[0]),
1222
+ end: view.clock(proposal()[1])
1223
+ }),
1224
+ _v$37 = view.messages().calendarCreateAt({
1225
+ start: view.clock(proposal()[0]),
1226
+ end: view.clock(proposal()[1])
1227
+ });
1228
+ _v$32 !== _p$.e && _$setAttribute(_el$26, "tabindex", _p$.e = _v$32);
1229
+ _v$33 !== _p$.t && _$setAttribute(_el$26, "data-stop", _p$.t = _v$33);
1230
+ _v$34 !== _p$.a && _$setAttribute(_el$26, "data-col", _p$.a = _v$34);
1231
+ _v$35 !== _p$.o && _$setAttribute(_el$26, "data-minute", _p$.o = _v$35);
1232
+ _v$36 !== _p$.i && _$setAttribute(_el$26, "aria-label", _p$.i = _v$36);
1233
+ _v$37 !== _p$.n && _$setAttribute(_el$26, "title", _p$.n = _v$37);
1234
+ return _p$;
1235
+ }, {
1236
+ e: undefined,
1237
+ t: undefined,
1238
+ a: undefined,
1239
+ o: undefined,
1240
+ i: undefined,
1241
+ n: undefined
1242
+ });
1243
+ _$runHydrationEvents();
1244
+ return _el$26;
1245
+ }
1246
+ }), _el$30, _co$7);
1247
+ _$effect(_p$ => {
1248
+ var _v$38 = labelled() ? undefined : "true",
1249
+ _v$39 = length() >= GAP_LONG_MINUTES ? "true" : undefined,
1250
+ _v$40 = props.gap.past ? "true" : undefined,
1251
+ _v$41 = props.aside ? "true" : undefined,
1252
+ _v$42 = hit() ? hit().fits ? "fit" : "short" : undefined,
1253
+ _v$43 = termsCss(top()),
1254
+ _v$44 = termsCss(subtractTerms(dayYTerms(view.wake(), props.expanded, props.gap.end), top()));
1255
+ _v$38 !== _p$.e && _$setAttribute(_el$24, "data-short", _p$.e = _v$38);
1256
+ _v$39 !== _p$.t && _$setAttribute(_el$24, "data-long", _p$.t = _v$39);
1257
+ _v$40 !== _p$.a && _$setAttribute(_el$24, "data-past", _p$.a = _v$40);
1258
+ _v$41 !== _p$.o && _$setAttribute(_el$24, "data-aside", _p$.o = _v$41);
1259
+ _v$42 !== _p$.i && _$setAttribute(_el$24, "data-hit", _p$.i = _v$42);
1260
+ _v$43 !== _p$.n && _$setStyleProperty(_el$24, "top", _p$.n = _v$43);
1261
+ _v$44 !== _p$.s && _$setStyleProperty(_el$24, "height", _p$.s = _v$44);
1262
+ return _p$;
1263
+ }, {
1264
+ e: undefined,
1265
+ t: undefined,
1266
+ a: undefined,
1267
+ o: undefined,
1268
+ i: undefined,
1269
+ n: undefined,
1270
+ s: undefined
1271
+ });
1272
+ return _el$24;
1273
+ })();
1274
+ };
1275
+
1276
+ /** The preview of a drag, a draw, or an entry dragged in, and the selected slot. */
1277
+ export const TimeOverlays = props => {
1278
+ const view = props.view;
1279
+ const ghost = () => {
1280
+ const current = view.ghost();
1281
+ return current && current.key === props.dayKey ? current : null;
1282
+ };
1283
+ const selected = () => {
1284
+ const current = view.selection();
1285
+ return current && current.key === props.dayKey && current.start !== undefined && current.end !== undefined ? current : null;
1286
+ };
1287
+ const draft = () => {
1288
+ const current = view.touchDraft();
1289
+ return current && current.key === props.dayKey ? current : null;
1290
+ };
1291
+ const box = (start, end) => {
1292
+ const top = dayTopTerms(view.wake(), props.expanded, start);
1293
+ return {
1294
+ top: termsCss(top),
1295
+ height: termsCss(subtractTerms(dayYTerms(view.wake(), props.expanded, end), top))
1296
+ };
1297
+ };
1298
+ return [_$createComponent(Show, {
1299
+ get when() {
1300
+ return selected();
1301
+ },
1302
+ children: slot => (() => {
1303
+ var _el$31 = _$getNextElement(_tmpl$10);
1304
+ _$effect(_$p => _$style(_el$31, box(slot().start, slot().end), _$p));
1305
+ return _el$31;
1306
+ })()
1307
+ }), _$createComponent(Show, {
1308
+ get when() {
1309
+ return draft();
1310
+ },
1311
+ children: slot => (() => {
1312
+ var _el$32 = _$getNextElement(_tmpl$11),
1313
+ _el$33 = _el$32.firstChild,
1314
+ _el$34 = _el$33.nextSibling,
1315
+ _el$35 = _el$34.firstChild,
1316
+ _el$36 = _el$35.nextSibling,
1317
+ _el$39 = _el$36.firstChild,
1318
+ [_el$40, _co$8] = _$getNextMarker(_el$39.nextSibling),
1319
+ _el$37 = _el$40.nextSibling,
1320
+ _el$41 = _el$37.nextSibling,
1321
+ [_el$42, _co$9] = _$getNextMarker(_el$41.nextSibling),
1322
+ _el$38 = _el$42.nextSibling,
1323
+ _el$43 = _el$38.nextSibling,
1324
+ [_el$44, _co$0] = _$getNextMarker(_el$43.nextSibling),
1325
+ _el$45 = _el$34.nextSibling;
1326
+ _el$33.$$pointerdown = event => startDraftResize(view, event, "start");
1327
+ _el$34.$$click = event => view.openQuickCreate(slot().key, slot().start, slot().end, event.currentTarget);
1328
+ _$insert(_el$36, () => view.clock(slot().start), _el$40, _co$8);
1329
+ _$insert(_el$36, () => view.clock(slot().end), _el$42, _co$9);
1330
+ _$insert(_el$36, () => view.duration(slot().end - slot().start), _el$44, _co$0);
1331
+ _el$45.$$pointerdown = event => startDraftResize(view, event, "end");
1332
+ _$effect(_p$ => {
1333
+ var _v$45 = box(slot().start, slot().end),
1334
+ _v$46 = view.messages().calendarCreateAt({
1335
+ start: view.clock(slot().start),
1336
+ end: view.clock(slot().end)
1337
+ });
1338
+ _p$.e = _$style(_el$32, _v$45, _p$.e);
1339
+ _v$46 !== _p$.t && _$setAttribute(_el$34, "aria-label", _p$.t = _v$46);
1340
+ return _p$;
1341
+ }, {
1342
+ e: undefined,
1343
+ t: undefined
1344
+ });
1345
+ _$runHydrationEvents();
1346
+ return _el$32;
1347
+ })()
1348
+ }), _$createComponent(Show, {
1349
+ get when() {
1350
+ return ghost();
1351
+ },
1352
+ children: current => (() => {
1353
+ var _el$46 = _$getNextElement(_tmpl$12),
1354
+ _el$47 = _el$46.firstChild,
1355
+ _el$48 = _el$47.nextSibling,
1356
+ _el$49 = _el$48.nextSibling,
1357
+ [_el$50, _co$1] = _$getNextMarker(_el$49.nextSibling);
1358
+ _$insert(_el$47, () => current().title);
1359
+ _$insert(_el$48, () => current().time);
1360
+ _$insert(_el$46, _$createComponent(Show, {
1361
+ get when() {
1362
+ return current().fit;
1363
+ },
1364
+ children: fit => (() => {
1365
+ var _el$51 = _$getNextElement(_tmpl$13);
1366
+ _$insert(_el$51, () => fit().text);
1367
+ _$effect(() => _$setAttribute(_el$51, "data-fit", fit().fits ? "fit" : "short"));
1368
+ return _el$51;
1369
+ })()
1370
+ }), _el$50, _co$1);
1371
+ _$effect(_p$ => {
1372
+ var _v$47 = current().end - current().start < 40 ? "s" : "m",
1373
+ _v$48 = current().warn ? "true" : undefined,
1374
+ _v$49 = {
1375
+ ...box(current().start, current().end),
1376
+ "--k2b-calendar-accent": current().colorHex ?? "var(--k2b-action)"
1377
+ };
1378
+ _v$47 !== _p$.e && _$setAttribute(_el$46, "data-size", _p$.e = _v$47);
1379
+ _v$48 !== _p$.t && _$setAttribute(_el$46, "data-warn", _p$.t = _v$48);
1380
+ _p$.a = _$style(_el$46, _v$49, _p$.a);
1381
+ return _p$;
1382
+ }, {
1383
+ e: undefined,
1384
+ t: undefined,
1385
+ a: undefined
1386
+ });
1387
+ return _el$46;
1388
+ })()
1389
+ })];
1390
+ };
1391
+
1392
+ /** The handles of a tapped slot change its start or end. */
1393
+ const startDraftResize = (view, pointerEvent, edge) => {
1394
+ const draft = view.touchDraft();
1395
+ if (!draft) return;
1396
+ pointerEvent.preventDefault();
1397
+ pointerEvent.stopPropagation();
1398
+ startCalendarPointerSession({
1399
+ event: pointerEvent,
1400
+ threshold: 1,
1401
+ resolve: (clientX, clientY) => {
1402
+ const hit = view.bodyAt(clientX, clientY);
1403
+ if (!hit || hit.key !== draft.key) return null;
1404
+ const minute = snapMinutes(hit.minute, 15);
1405
+ const current = view.touchDraft() ?? draft;
1406
+ return edge === "end" ? {
1407
+ ...current,
1408
+ end: Math.max(current.start + 15, Math.min(view.limitOf(draft.key), minute))
1409
+ } : {
1410
+ ...current,
1411
+ start: Math.max(0, Math.min(current.end - 15, minute))
1412
+ };
1413
+ },
1414
+ onPreview: next => {
1415
+ view.setTouchDraft(next);
1416
+ view.setSelection({
1417
+ key: next.key,
1418
+ start: next.start,
1419
+ end: next.end
1420
+ });
1421
+ },
1422
+ onCommit: () => swallowNextClick()
1423
+ });
1424
+ };
1425
+
1426
+ /** Free time of a day with the gap of now trimmed, as both views draw it. */
1427
+ export const dayGaps = (view, key) => freeGaps(view.dayOf(key).bands, view.wake(), view.nowIn(key));
1428
+
1429
+ /** The bands of a day with their lanes, cut to what its timeline draws. */
1430
+ export const dayBands = (view, key, expanded) => {
1431
+ const limit = expanded ? DAY_MINUTES : view.wake();
1432
+ const visible = view.dayOf(key).bands.filter(band => Math.min(limit, band.end) > Math.max(0, band.start));
1433
+ return dayLanes(visible, LANE_MINUTES);
1434
+ };
1435
+
1436
+ /** Entries the folded night hides, as chips with their time. */
1437
+ export const foldedEntries = (view, key) => {
1438
+ const model = view.dayOf(key);
1439
+ const wake = view.wake();
1440
+ return [...model.bands.filter(band => band.end > wake).map(band => ({
1441
+ event: band.item,
1442
+ time: band.start >= wake ? view.clock(band.start) : view.messages().calendarUntil({
1443
+ time: view.clock(band.end)
1444
+ })
1445
+ })), ...model.markers.filter(marker => marker.minute >= wake).map(marker => ({
1446
+ event: marker.item,
1447
+ time: view.clock(marker.minute)
1448
+ }))];
1449
+ };
1450
+
1451
+ /** The quick create at a slot or gap, as in the month view. */
1452
+ export const TimeQuickCreate = props => {
1453
+ const view = props.view;
1454
+ let element;
1455
+ let returnFocus = null;
1456
+ createEffect(() => {
1457
+ const current = view.quick();
1458
+ if (!element) return;
1459
+ if (!current) {
1460
+ if (element.matches(":popover-open")) element.hidePopover();
1461
+ return;
1462
+ }
1463
+ returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1464
+ if (!element.matches(":popover-open")) element.showPopover();
1465
+ queueMicrotask(() => {
1466
+ if (view.quick() !== current || !element?.isConnected) return;
1467
+ placeCalendarPopover(current.anchor, element);
1468
+ element.querySelector("input,textarea,select,button,a[href],[tabindex]:not([tabindex='-1'])")?.focus();
1469
+ });
1470
+ });
1471
+ /** Whether the focus is in the popover, or fell to the page as it hid: only then does a close return it. */
1472
+ const focusInPopover = () => element?.contains(document.activeElement) || document.activeElement === document.body;
1473
+ const close = restore => {
1474
+ const current = view.quick();
1475
+ if (!current) return;
1476
+ const selection = view.selection();
1477
+ view.setQuick(null);
1478
+ // A late close must leave a newer selection in place.
1479
+ if (selection?.key === current.key && selection.start === current.start && selection.end === current.end) view.setSelection(null);
1480
+ if (restore && returnFocus?.isConnected) returnFocus.focus({
1481
+ preventScroll: true
1482
+ });
1483
+ };
1484
+ return (() => {
1485
+ var _el$52 = _$getNextElement(_tmpl$14);
1486
+ _el$52.addEventListener("toggle", event => {
1487
+ if (event.newState !== "closed" || !view.quick() || event.currentTarget.matches(":popover-open")) return;
1488
+ close(focusInPopover());
1489
+ });
1490
+ var _ref$ = element;
1491
+ typeof _ref$ === "function" ? _$use(_ref$, _el$52) : element = _el$52;
1492
+ _$insert(_el$52, _$createComponent(Show, {
1493
+ get when() {
1494
+ return view.quick();
1495
+ },
1496
+ keyed: true,
1497
+ children: value => view.props.owner.renderQuickCreate?.(value.range, {
1498
+ create: value.create,
1499
+ close: () => {
1500
+ if (view.quick() === value) close(focusInPopover());
1501
+ }
1502
+ })
1503
+ }));
1504
+ _$effect(() => _$setAttribute(_el$52, "aria-label", view.messages().calendarQuickCreate));
1505
+ return _el$52;
1506
+ })();
1507
+ };
1508
+
1509
+ /** All-day entries of a day as chips: bars with torn ends where they go on, and points due that day. */
1510
+ export const AllDayChips = props => {
1511
+ const view = props.view;
1512
+ return _$createComponent(For, {
1513
+ get each() {
1514
+ return props.spans;
1515
+ },
1516
+ children: span => {
1517
+ const event = span.item;
1518
+ const checked = () => view.checkedOf(event);
1519
+ return (() => {
1520
+ var _el$53 = _$getNextElement(_tmpl$15),
1521
+ _el$54 = _el$53.firstChild,
1522
+ [_el$55, _co$10] = _$getNextMarker(_el$54.nextSibling),
1523
+ _el$56 = _el$55.nextSibling,
1524
+ [_el$57, _co$11] = _$getNextMarker(_el$56.nextSibling);
1525
+ _$insert(_el$53, _$createComponent(Show, {
1526
+ get when() {
1527
+ return checked() !== undefined;
1528
+ },
1529
+ get children() {
1530
+ return _$createComponent(TimeCheck, {
1531
+ view: view,
1532
+ event: event
1533
+ });
1534
+ }
1535
+ }), _el$55, _co$10);
1536
+ _$insert(_el$53, _$createComponent(EventChip, {
1537
+ get event() {
1538
+ return view.normalized(event, props.dayKey);
1539
+ },
1540
+ get owner() {
1541
+ return view.props.owner;
1542
+ },
1543
+ get href() {
1544
+ return eventHref(view.props.owner, event);
1545
+ },
1546
+ compact: true,
1547
+ timeline: true,
1548
+ get bar() {
1549
+ return !span.marker;
1550
+ },
1551
+ get checkbox() {
1552
+ return checked() !== undefined;
1553
+ },
1554
+ get onCheck() {
1555
+ return view.checkOf(event);
1556
+ },
1557
+ get continuesBefore() {
1558
+ return span.firstKey < props.dayKey;
1559
+ },
1560
+ get continuesAfter() {
1561
+ return span.lastKey > props.dayKey;
1562
+ }
1563
+ }), _el$57, _co$11);
1564
+ _$effect(_p$ => {
1565
+ var _v$50 = span.marker ? undefined : "true",
1566
+ _v$51 = checked() !== undefined ? "true" : undefined,
1567
+ _v$52 = checked() ? "true" : undefined,
1568
+ _v$53 = view.justChecked().has(event.id) ? "true" : undefined,
1569
+ _v$54 = eventAccent(event);
1570
+ _v$50 !== _p$.e && _$setAttribute(_el$53, "data-bar", _p$.e = _v$50);
1571
+ _v$51 !== _p$.t && _$setAttribute(_el$53, "data-checkbox", _p$.t = _v$51);
1572
+ _v$52 !== _p$.a && _$setAttribute(_el$53, "data-checked", _p$.a = _v$52);
1573
+ _v$53 !== _p$.o && _$setAttribute(_el$53, "data-just-checked", _p$.o = _v$53);
1574
+ _v$54 !== _p$.i && _$setStyleProperty(_el$53, "--k2b-calendar-accent", _p$.i = _v$54);
1575
+ return _p$;
1576
+ }, {
1577
+ e: undefined,
1578
+ t: undefined,
1579
+ a: undefined,
1580
+ o: undefined,
1581
+ i: undefined
1582
+ });
1583
+ return _el$53;
1584
+ })();
1585
+ }
1586
+ });
1587
+ };
1588
+
1589
+ /** Hour labels and lines of a day's timeline with the night open, and the date at midnight. */
1590
+ export const nightHours = wake => {
1591
+ const hours = [];
1592
+ for (let minute = wake + 60; minute < DAY_MINUTES; minute += 60) hours.push(minute);
1593
+ return hours;
1594
+ };
1595
+ export const isNearNow = (view, key, minute) => key === view.nowKey() && Math.abs(view.nowMinute() - minute) < 14;
1596
+
1597
+ /** Whether a marker sits right above a band's start, so its pill goes above the line. */
1598
+ export const markerAbove = (model, minute) => model.bands.some(band => band.start >= minute - 10 && band.start <= minute + 20);
1599
+
1600
+ /** Whether a deadline pill sits near the middle of a gap, so the gap's label moves aside. */
1601
+ export const gapAside = (model, gap) => {
1602
+ const middle = (gap.start + gap.end) / 2;
1603
+ return model.markers.some(marker => marker.minute > gap.start - 10 && marker.minute < gap.end + 10 && Math.abs(marker.minute - middle) < 25);
1604
+ };
1605
+
1606
+ /** A day's counts for its header: events and all-day entries, to-dos and deadlines. */
1607
+ export const daySummary = (view, key) => {
1608
+ const model = view.dayOf(key);
1609
+ const isTask = event => event.display === "marker" || event.checked !== undefined;
1610
+ const all = [...model.bands.map(band => band.item), ...model.markers.map(marker => marker.item), ...model.spans.map(span => span.item)];
1611
+ const tasks = all.filter(isTask).length;
1612
+ return view.messages().calendarDaySummary({
1613
+ events: all.length - tasks,
1614
+ tasks
1615
+ });
1616
+ };
1617
+
1618
+ /** What starts within the next hour today, such as "Planning in 12 min". */
1619
+ export const soonNote = (view, key) => {
1620
+ if (key !== view.nowKey()) return "";
1621
+ const now = view.nowMinute();
1622
+ const next = view.dayOf(key).bands.filter(band => band.start > now && band.start - now <= 60 && !view.checkedOf(band.item)).sort((left, right) => left.start - right.start)[0];
1623
+ return next ? view.messages().calendarSoon({
1624
+ title: next.item.title,
1625
+ duration: view.duration(next.start - now)
1626
+ }) : "";
1627
+ };
1628
+
1629
+ /** The busy minutes of a day's waking hours, merged, with the color of the first entry, for the thin load line. */
1630
+ export const dayLoad = (view, key) => {
1631
+ const pieces = view.dayOf(key).bands.map(band => ({
1632
+ start: Math.max(0, band.start),
1633
+ end: Math.min(view.wake(), band.end),
1634
+ color: eventAccent(band.item)
1635
+ })).filter(piece => piece.end > piece.start).sort((left, right) => left.start - right.start);
1636
+ const merged = [];
1637
+ for (const piece of pieces) {
1638
+ const last = merged[merged.length - 1];
1639
+ if (last && piece.start <= last.end) last.end = Math.max(last.end, piece.end);else merged.push({
1640
+ ...piece
1641
+ });
1642
+ }
1643
+ return merged;
1644
+ };
1645
+
1646
+ /** Day keys of the week that holds a day, from its first day. */
1647
+ export const weekKeysOf = (view, key) => {
1648
+ const start = view.keyOf(calendar.startOfWeek(view.parseKey(key), view.dateConfig()));
1649
+ return Array.from({
1650
+ length: 7
1651
+ }, (_, index) => addDayKeys(start, index));
1652
+ };
1653
+ _$delegateEvents(["pointerdown", "click"]);
1654
+ //# sourceMappingURL=calendar-time-view.js.map