@k2b/ui 0.20.0 → 0.21.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.
- package/dist/browser/actions/Button.js +20 -31
- package/dist/browser/actions/Button.js.map +1 -1
- package/dist/browser/actions/ContextMenu.js +7 -3
- package/dist/browser/actions/ContextMenu.js.map +1 -1
- package/dist/browser/actions/CopyButton.js +10 -8
- package/dist/browser/actions/CopyButton.js.map +1 -1
- package/dist/browser/actions/GestureMenu.js +4 -0
- package/dist/browser/actions/GestureMenu.js.map +1 -1
- package/dist/browser/content/Calendar.js +1951 -565
- package/dist/browser/content/Calendar.js.map +1 -1
- package/dist/browser/content/calendar-messages.js +83 -0
- package/dist/browser/content/calendar-messages.js.map +1 -0
- package/dist/browser/content/calendar-month-layout.js +108 -0
- package/dist/browser/content/calendar-month-layout.js.map +1 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/inputs/DatePicker.js +46 -10
- package/dist/browser/inputs/DatePicker.js.map +1 -1
- package/dist/browser/inputs/date-picker.js +24 -0
- package/dist/browser/inputs/date-picker.js.map +1 -1
- package/dist/browser/intl/messages.js +6 -0
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/ssr/actions/Button.js +21 -32
- package/dist/ssr/actions/Button.js.map +1 -1
- package/dist/ssr/actions/ContextMenu.js +7 -3
- package/dist/ssr/actions/ContextMenu.js.map +1 -1
- package/dist/ssr/actions/CopyButton.js +9 -7
- package/dist/ssr/actions/CopyButton.js.map +1 -1
- package/dist/ssr/actions/GestureMenu.js +4 -0
- package/dist/ssr/actions/GestureMenu.js.map +1 -1
- package/dist/ssr/content/Calendar.js +1223 -105
- package/dist/ssr/content/Calendar.js.map +1 -1
- package/dist/ssr/content/calendar-messages.js +83 -0
- package/dist/ssr/content/calendar-messages.js.map +1 -0
- package/dist/ssr/content/calendar-month-layout.js +108 -0
- package/dist/ssr/content/calendar-month-layout.js.map +1 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/inputs/DatePicker.js +34 -4
- package/dist/ssr/inputs/DatePicker.js.map +1 -1
- package/dist/ssr/inputs/date-picker.js +24 -0
- package/dist/ssr/inputs/date-picker.js.map +1 -1
- package/dist/ssr/intl/messages.js +6 -0
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/actions/GestureMenu.d.ts +2 -0
- package/dist/types/content/Calendar.d.ts +32 -0
- package/dist/types/content/calendar-messages.d.ts +41 -0
- package/dist/types/content/calendar-month-layout.d.ts +52 -0
- package/dist/types/content/index.d.ts +1 -1
- package/dist/types/inputs/date-picker.d.ts +7 -0
- package/dist/types/intl/messages.d.ts +3 -0
- package/package.json +1 -1
|
@@ -4,61 +4,85 @@ import { ssrElement as _$ssrElement } from "solid-js/web";
|
|
|
4
4
|
import { mergeProps as _$mergeProps } from "solid-js/web";
|
|
5
5
|
import { ssrStyle as _$ssrStyle } from "solid-js/web";
|
|
6
6
|
import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
|
|
7
|
-
import { escape as _$escape } from "solid-js/web";
|
|
8
7
|
import { createComponent as _$createComponent } from "solid-js/web";
|
|
9
8
|
import { ssr as _$ssr } from "solid-js/web";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
_tmpl$
|
|
13
|
-
_tmpl$
|
|
14
|
-
_tmpl$
|
|
15
|
-
_tmpl$
|
|
16
|
-
_tmpl$
|
|
17
|
-
_tmpl$
|
|
18
|
-
_tmpl$
|
|
19
|
-
_tmpl$
|
|
20
|
-
_tmpl$
|
|
21
|
-
_tmpl$
|
|
22
|
-
_tmpl$
|
|
23
|
-
_tmpl$
|
|
24
|
-
_tmpl$
|
|
25
|
-
_tmpl$
|
|
26
|
-
_tmpl$
|
|
27
|
-
_tmpl$
|
|
28
|
-
_tmpl$
|
|
29
|
-
_tmpl$
|
|
30
|
-
_tmpl$
|
|
31
|
-
_tmpl$
|
|
32
|
-
_tmpl$
|
|
33
|
-
_tmpl$
|
|
34
|
-
_tmpl$
|
|
35
|
-
_tmpl$
|
|
36
|
-
_tmpl$
|
|
37
|
-
_tmpl$
|
|
38
|
-
_tmpl$
|
|
39
|
-
_tmpl$
|
|
40
|
-
_tmpl$
|
|
41
|
-
_tmpl$
|
|
42
|
-
_tmpl$
|
|
43
|
-
_tmpl$
|
|
44
|
-
_tmpl$
|
|
45
|
-
_tmpl$
|
|
46
|
-
_tmpl$
|
|
47
|
-
_tmpl$
|
|
48
|
-
_tmpl$
|
|
49
|
-
_tmpl$
|
|
50
|
-
_tmpl$
|
|
51
|
-
_tmpl$
|
|
52
|
-
_tmpl$
|
|
53
|
-
_tmpl$
|
|
9
|
+
import { escape as _$escape } from "solid-js/web";
|
|
10
|
+
var _tmpl$ = ["<span class=\"k2b-calendar-event__time\">", "</span>"],
|
|
11
|
+
_tmpl$2 = "<span class=\"k2b-calendar-event__marker\" aria-hidden=\"true\"></span>",
|
|
12
|
+
_tmpl$3 = ["<span class=\"k2b-calendar-event__title\">", "", "</span>"],
|
|
13
|
+
_tmpl$4 = ["<span class=\"k2b-calendar-event__meta\">", "</span>"],
|
|
14
|
+
_tmpl$5 = ["<span class=\"k2b-calendar-event__description\">", "</span>"],
|
|
15
|
+
_tmpl$6 = ["<span class=\"k2b-calendar-event__hint\" data-edge=\"start\" aria-hidden=\"true\">", "</span>"],
|
|
16
|
+
_tmpl$7 = ["<span class=\"k2b-calendar-event__hint\" data-edge=\"end\" aria-hidden=\"true\">", "</span>"],
|
|
17
|
+
_tmpl$8 = ["<a", " class=\"k2b-calendar-event\" data-calendar-event", " style=\"", "\"", ">", "</a>"],
|
|
18
|
+
_tmpl$9 = ["<button type=\"button\" class=\"k2b-calendar-event\" data-calendar-event", " data-interactive=\"true\"", " style=\"", "\"", ">", "</button>"],
|
|
19
|
+
_tmpl$0 = ["<div class=\"k2b-calendar-event\" role=\"group\" data-calendar-event", " style=\"", "\"", ">", "</div>"],
|
|
20
|
+
_tmpl$1 = ["<div role=\"radiogroup\"", " aria-orientation=\"horizontal\" class=\"k2b-segmented-control k2b-calendar-view-switcher\">", "</div>"],
|
|
21
|
+
_tmpl$10 = ["<i class=\"", "\"></i>"],
|
|
22
|
+
_tmpl$11 = ["<button type=\"button\"", " class=\"k2b-calendar-header__nav-button\"><i class=\"", "\"></i></button>"],
|
|
23
|
+
_tmpl$12 = ["<span class=\"k2b-button__label\">", "</span>"],
|
|
24
|
+
_tmpl$13 = ["<div class=\"k2b-calendar-header__content\">", "</div>"],
|
|
25
|
+
_tmpl$14 = "<span class=\"k2b-calendar-header__progress\" aria-hidden=\"true\"></span>",
|
|
26
|
+
_tmpl$15 = ["<header class=\"k2b-calendar-header\"><div class=\"k2b-calendar-header__navigation\">", "", "<div class=\"k2b-calendar-header__title\"><span>", "</span>", "</div></div>", "<div class=\"k2b-calendar-header__actions\">", "", "", "</div>", "</header>"],
|
|
27
|
+
_tmpl$16 = ["<span class=\"k2b-calendar-header__title-size\" aria-hidden=\"true\">", "</span>"],
|
|
28
|
+
_tmpl$17 = ["<div class=\"k2b-calendar-month__weekday\">", "</div>"],
|
|
29
|
+
_tmpl$18 = ["<div class=\"k2b-calendar-month\" style=\"", "\"><div class=\"k2b-calendar-month__weekdays\"", ">", "", "</div>", "</div>"],
|
|
30
|
+
_tmpl$19 = ["<div class=\"k2b-calendar-month__week-number\">", "</div>"],
|
|
31
|
+
_tmpl$20 = ["<div class=\"k2b-calendar-month__week\"", ">", "", "</div>"],
|
|
32
|
+
_tmpl$21 = ["<div class=\"k2b-calendar-preview\">", "</div>"],
|
|
33
|
+
_tmpl$22 = ["<div class=\"k2b-calendar-month__day\"", ">", "<div class=\"k2b-calendar-month__day-header\">", "", "</div><div class=\"k2b-calendar-month__events\">", "", "", "</div><div class=\"k2b-calendar-month__dots\">", "</div></div>"],
|
|
34
|
+
_tmpl$23 = ["<span class=\"k2b-calendar-month__day-number\"", ">", "</span>"],
|
|
35
|
+
_tmpl$24 = ["<span class=\"k2b-calendar-month__badge\">", "", "</span>"],
|
|
36
|
+
_tmpl$25 = ["<span class=\"k2b-calendar-dot\"", " style=\"", "\"></span>"],
|
|
37
|
+
_tmpl$26 = ["<div class=\"k2b-calendar-month__weekday\" role=\"columnheader\">", "</div>"],
|
|
38
|
+
_tmpl$27 = ["<div class=\"k2b-calendar-month\" role=\"grid\"", " aria-multiselectable=\"true\" data-lanes=\"true\" style=\"", "\"><div class=\"k2b-calendar-month__weekdays\" role=\"row\"", ">", "", "</div>", "</div>"],
|
|
39
|
+
_tmpl$28 = ["<div class=\"k2b-calendar-month__weekday\" role=\"columnheader\"", ">", "</div>"],
|
|
40
|
+
_tmpl$29 = ["<div class=\"k2b-calendar-month__week-number\" role=\"rowheader\">", "</div>"],
|
|
41
|
+
_tmpl$30 = ["<div class=\"k2b-calendar-month__week\" role=\"row\"", ">", "", "</div>"],
|
|
42
|
+
_tmpl$31 = ["<button type=\"button\" class=\"k2b-calendar-month__week-link\" tabindex=\"-1\"", ">", "</button>"],
|
|
43
|
+
_tmpl$32 = ["<button type=\"button\" class=\"k2b-calendar-month__more\" tabindex=\"-1\" style=\"", "\"", " aria-haspopup=\"dialog\"", "><span class=\"k2b-calendar-month__more-long\">", "</span><span class=\"k2b-calendar-month__more-short\">+", "</span></button>"],
|
|
44
|
+
_tmpl$33 = ["<div class=\"k2b-calendar-month__day\" role=\"gridcell\"", "><div class=\"k2b-calendar-month__day-header\"><span class=\"k2b-calendar-month__day-number\"", ">", "</span>", "</div><div class=\"k2b-calendar-month__lanes\">", "", "", "</div></div>"],
|
|
45
|
+
_tmpl$34 = ["<div class=\"k2b-calendar-month__segment\" style=\"", "\">", "</div>"],
|
|
46
|
+
_tmpl$35 = ["<ul class=\"k2b-calendar-day-list__events\">", "</ul>"],
|
|
47
|
+
_tmpl$36 = "<i class=\"ti ti-plus\" aria-hidden=\"true\"></i>",
|
|
48
|
+
_tmpl$37 = ["<div class=\"k2b-calendar-day-list\"><div class=\"k2b-calendar-day-list__title\">", "</div>", "<div class=\"k2b-calendar-day-list__actions\">", "", "</div></div>"],
|
|
49
|
+
_tmpl$38 = ["<p class=\"k2b-calendar-day-list__empty\">", "</p>"],
|
|
50
|
+
_tmpl$39 = ["<li>", "</li>"],
|
|
51
|
+
_tmpl$40 = "<i class=\"ti ti-zoom-in\" aria-hidden=\"true\"></i>",
|
|
52
|
+
_tmpl$41 = ["<div class=\"k2b-calendar-popover\" popover=\"auto\" role=\"dialog\"", ">", "</div>"],
|
|
53
|
+
_tmpl$42 = ["<div class=\"k2b-calendar-time-grid__all-day\" style=\"", "\"><div class=\"k2b-calendar-time-grid__all-day-label\">", "</div>", "</div>"],
|
|
54
|
+
_tmpl$43 = ["<div class=\"k2b-calendar-time-grid\"><div class=\"k2b-calendar-time-grid__days\" style=\"", "\"><div></div>", "</div>", "<div class=\"k2b-calendar-time-grid__scroll\"><div class=\"k2b-calendar-time-grid__grid\" style=\"", "\"><div class=\"k2b-calendar-time-grid__gutter\" style=\"", "\">", "</div>", "</div></div></div>"],
|
|
55
|
+
_tmpl$44 = ["<span class=\"k2b-calendar-time-grid__day-label\"", ">", "</span>"],
|
|
56
|
+
_tmpl$45 = ["<span class=\"k2b-calendar-time-grid__badge\">", "", "</span>"],
|
|
57
|
+
_tmpl$46 = ["<div class=\"k2b-calendar-time-grid__all-day-events\">", "", "</div>"],
|
|
58
|
+
_tmpl$47 = ["<div class=\"k2b-calendar-time-grid__hour\"", ">", ":00</div>"],
|
|
59
|
+
_tmpl$48 = ["<div class=\"k2b-calendar-time-grid__now\" style=\"", "\"></div>"],
|
|
60
|
+
_tmpl$49 = ["<div class=\"k2b-calendar-time-grid__column\" style=\"", "\">", "", "", "", "", "</div>"],
|
|
61
|
+
_tmpl$50 = ["<div class=\"k2b-calendar-preview__title\">", "</div>"],
|
|
62
|
+
_tmpl$51 = ["<div class=\"k2b-calendar-preview k2b-calendar-preview--timed\"", " style=\"", "\"><div class=\"k2b-calendar-preview__content\">", "<div class=\"k2b-calendar-preview__label\">", " - ", "</div></div></div>"],
|
|
63
|
+
_tmpl$52 = ["<button type=\"button\"", " draggable=\"false\" class=\"k2b-calendar-time-grid__resize\"><span class=\"k2b-calendar-time-grid__resize-icon\" aria-hidden=\"true\"></span></button>"],
|
|
64
|
+
_tmpl$53 = ["<div class=\"k2b-calendar-time-grid__event-position\" style=\"", "\"><div class=\"k2b-calendar-time-grid__event-frame\">", "", "</div></div>"],
|
|
65
|
+
_tmpl$54 = ["<button type=\"button\" class=\"k2b-calendar-time-grid__overflow\" style=\"", "\"", "><span class=\"k2b-calendar-time-grid__overflow-count\">+", "</span></button>"],
|
|
66
|
+
_tmpl$55 = ["<div class=\"k2b-calendar-time-grid__overflow-menu\" style=\"", "\"><div class=\"k2b-calendar-time-grid__overflow-title\">", " - ", "</div>", "</div>"],
|
|
67
|
+
_tmpl$56 = ["<div class=\"k2b-calendar-year\">", "</div>"],
|
|
68
|
+
_tmpl$57 = ["<div class=\"k2b-calendar-year__month\"><div class=\"k2b-calendar-year__title\">", "</div><div class=\"k2b-calendar-year__grid\">", "</div></div>"],
|
|
69
|
+
_tmpl$58 = ["<span class=\"k2b-calendar-year__indicator\"", " style=\"", "\"></span>"],
|
|
70
|
+
_tmpl$59 = ["<div class=\"k2b-calendar-mobile-month__events\">", "</div>"],
|
|
71
|
+
_tmpl$60 = ["<div class=\"k2b-calendar-mobile-month\">", "<div class=\"k2b-calendar-mobile-month__agenda\"><div class=\"k2b-calendar-mobile-month__title\">", "</div>", "</div></div>"],
|
|
72
|
+
_tmpl$61 = ["<div class=\"k2b-calendar-mobile-month__empty\">", "</div>"],
|
|
73
|
+
_tmpl$62 = ["<div class=\"k2b-calendar-body\">", "</div>"],
|
|
74
|
+
_tmpl$63 = ["<section class=\"", "\"", ">", "", "</section>"];
|
|
54
75
|
import { Link } from "@k2b/ssr/nav";
|
|
55
76
|
import { dates as calendar } from "@k2b/stdlib";
|
|
56
|
-
import { createMemo, createSignal, For, mergeProps, onCleanup, onMount, Show } from "solid-js";
|
|
77
|
+
import { createEffect, createMemo, createSignal, For, mergeProps, on, onCleanup, onMount, Show, untrack } from "solid-js";
|
|
57
78
|
import { Button } from "../actions/Button";
|
|
79
|
+
import { GestureMenu } from "../actions/GestureMenu";
|
|
58
80
|
import SegmentedControl from "../actions/SegmentedControl";
|
|
59
81
|
import { useDateConfigLocale } from "../intl/locale";
|
|
60
82
|
import { useUiMessages } from "../intl/messages";
|
|
61
83
|
import { layoutCalendarIntervals } from "./calendar-event-layout";
|
|
84
|
+
import { useCalendarMessages } from "./calendar-messages";
|
|
85
|
+
import { fitMonthWeek, layoutMonthWeek, monthLaneCapacity } from "./calendar-month-layout";
|
|
62
86
|
import { calendarDayIndexAtPoint, calendarMinuteAtPoint, startCalendarPointerSession } from "./calendar-pointer";
|
|
63
87
|
|
|
64
88
|
/** A view the application adds to the switcher; the calendar shows its header and toolbar, the application the body. */
|
|
@@ -235,7 +259,7 @@ const EventChip = props => {
|
|
|
235
259
|
const showDescription = () => Boolean(props.event.description?.trim() && props.fill && !props.event.allDay && !props.compact && durationHours() >= 1.5);
|
|
236
260
|
const timeLabel = () => `${formatTime(props.event.startDate, dateConfig())} - ${formatTime(props.event.endDate, dateConfig())}`;
|
|
237
261
|
const ariaLabel = () => {
|
|
238
|
-
const label = props.event.allDay ? props.event.title : messages().calendarEventTime({
|
|
262
|
+
const label = props.label ? props.label : props.event.allDay ? props.event.title : messages().calendarEventTime({
|
|
239
263
|
title: props.event.title,
|
|
240
264
|
start: formatTime(props.event.startDate, dateConfig()),
|
|
241
265
|
end: formatTime(props.event.endDate, dateConfig())
|
|
@@ -249,38 +273,60 @@ const EventChip = props => {
|
|
|
249
273
|
end: props.event.endDate,
|
|
250
274
|
allDay: props.event.allDay ?? false,
|
|
251
275
|
durationHours: durationHours(),
|
|
252
|
-
timeLabel: timeLabel()
|
|
276
|
+
timeLabel: timeLabel(),
|
|
277
|
+
leadingTime: props.leadingTime
|
|
253
278
|
});
|
|
254
|
-
const defaultContent = [_$
|
|
279
|
+
const defaultContent = [_$createComponent(Show, {
|
|
280
|
+
get when() {
|
|
281
|
+
return props.leadingTime;
|
|
282
|
+
},
|
|
283
|
+
get children() {
|
|
284
|
+
return _$ssr(_tmpl$, _$escape(props.leadingTime));
|
|
285
|
+
}
|
|
286
|
+
}), _$ssr(_tmpl$3, _$escape(_$createComponent(Show, {
|
|
255
287
|
get when() {
|
|
256
288
|
return props.event.display === "marker";
|
|
257
289
|
},
|
|
258
290
|
get children() {
|
|
259
|
-
return _$ssr(_tmpl$);
|
|
291
|
+
return _$ssr(_tmpl$2);
|
|
260
292
|
}
|
|
261
293
|
})), _$escape(props.event.title)), _$createComponent(Show, {
|
|
262
294
|
get when() {
|
|
263
295
|
return showTime();
|
|
264
296
|
},
|
|
265
297
|
get children() {
|
|
266
|
-
return _$ssr(_tmpl$
|
|
298
|
+
return _$ssr(_tmpl$4, _$escape(timeLabel()));
|
|
267
299
|
}
|
|
268
300
|
}), _$createComponent(Show, {
|
|
269
301
|
get when() {
|
|
270
302
|
return showLocation();
|
|
271
303
|
},
|
|
272
304
|
get children() {
|
|
273
|
-
return _$ssr(_tmpl$
|
|
305
|
+
return _$ssr(_tmpl$4, _$escape(props.event.location));
|
|
274
306
|
}
|
|
275
307
|
}), _$createComponent(Show, {
|
|
276
308
|
get when() {
|
|
277
309
|
return showDescription();
|
|
278
310
|
},
|
|
279
311
|
get children() {
|
|
280
|
-
return _$ssr(_tmpl$
|
|
312
|
+
return _$ssr(_tmpl$5, _$escape(props.event.description));
|
|
313
|
+
}
|
|
314
|
+
})];
|
|
315
|
+
const content = () => [_$createComponent(Show, {
|
|
316
|
+
get when() {
|
|
317
|
+
return props.hintStart;
|
|
318
|
+
},
|
|
319
|
+
get children() {
|
|
320
|
+
return _$ssr(_tmpl$6, _$escape(props.hintStart));
|
|
321
|
+
}
|
|
322
|
+
}), renderedEvent() ?? defaultContent, _$createComponent(Show, {
|
|
323
|
+
get when() {
|
|
324
|
+
return props.hintEnd;
|
|
325
|
+
},
|
|
326
|
+
get children() {
|
|
327
|
+
return _$ssr(_tmpl$7, _$escape(props.hintEnd));
|
|
281
328
|
}
|
|
282
329
|
})];
|
|
283
|
-
const content = () => renderedEvent() ?? defaultContent;
|
|
284
330
|
const isActionable = () => isInteractive() || Boolean(props.onMovePointerDown);
|
|
285
331
|
let suppressClickUntil = 0;
|
|
286
332
|
const onClick = event => {
|
|
@@ -312,7 +358,7 @@ const EventChip = props => {
|
|
|
312
358
|
suppressClickUntil = performance.now() + 400;
|
|
313
359
|
});
|
|
314
360
|
};
|
|
315
|
-
return props.href ? _$ssr(_tmpl$
|
|
361
|
+
return props.href ? _$ssr(_tmpl$8, _$ssrAttribute("href", _$escape(props.href, true), false), _$ssrAttribute("data-color", props.event.colorHex ? _$escape(undefined, true) : _$escape(color(), true), false) + _$ssrAttribute("data-selected", selected() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-compact", props.compact ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-fill", props.fill ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-short", short() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-moving", props.moving ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-continues-before", props.continuesBefore ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-continues-after", props.continuesAfter ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-bar", props.bar ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-interactive", isInteractive() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-display", _$escape(props.event.display, true), false) + _$ssrAttribute("data-space-item-id", _$escape(props.event.dataSpaceItemId, true), false), _$ssrStyle(style()), _$ssrAttribute("draggable", props.onMovePointerDown ? _$escape(false, true) : _$escape(undefined, true), false) + _$ssrAttribute("aria-label", _$escape(ariaLabel(), true), false), _$escape(content())) : isActionable() ? _$ssr(_tmpl$9, _$ssrAttribute("data-color", props.event.colorHex ? _$escape(undefined, true) : _$escape(color(), true), false) + _$ssrAttribute("data-selected", selected() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-compact", props.compact ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-fill", props.fill ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-short", short() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-moving", props.moving ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-continues-before", props.continuesBefore ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-continues-after", props.continuesAfter ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-bar", props.bar ? "true" : _$escape(undefined, true), false), _$ssrAttribute("data-display", _$escape(props.event.display, true), false), _$ssrStyle(style()), _$ssrAttribute("aria-label", _$escape(ariaLabel(), true), false), _$escape(content())) : _$ssr(_tmpl$0, _$ssrAttribute("data-color", props.event.colorHex ? _$escape(undefined, true) : _$escape(color(), true), false) + _$ssrAttribute("data-selected", selected() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-compact", props.compact ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-fill", props.fill ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-short", short() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-moving", props.moving ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-continues-before", props.continuesBefore ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-continues-after", props.continuesAfter ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-bar", props.bar ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-interactive", _$escape(undefined, true), false) + _$ssrAttribute("data-display", _$escape(props.event.display, true), false), _$ssrStyle(style()), _$ssrAttribute("aria-label", _$escape(ariaLabel(), true), false), _$escape(content()));
|
|
316
362
|
};
|
|
317
363
|
const slotInteractionProps = (owner, slot, suppressed, nativeControl = false) => {
|
|
318
364
|
const isSlotChild = event => {
|
|
@@ -395,13 +441,17 @@ const CalendarViewLinks = props => {
|
|
|
395
441
|
refs[event.key === "Home" ? 0 : props.options.length - 1]?.click();
|
|
396
442
|
}
|
|
397
443
|
};
|
|
398
|
-
return _$ssr(_tmpl$
|
|
444
|
+
return _$ssr(_tmpl$1, _$ssrAttribute("aria-label", _$escape(messages().calendarView, true), false), _$escape(_$createComponent(For, {
|
|
399
445
|
get each() {
|
|
400
446
|
return props.options;
|
|
401
447
|
},
|
|
402
448
|
children: (option, index) => {
|
|
403
449
|
const active = () => (props.view === "mobile-month" ? "month" : props.view) === option.value;
|
|
404
|
-
const href = () =>
|
|
450
|
+
const href = () => {
|
|
451
|
+
const day = props.focusDay;
|
|
452
|
+
const atDay = day && !active() ? props.owner.getDateHref?.(day, option.value) : undefined;
|
|
453
|
+
return atDay ?? props.owner.getViewHref?.(option.value) ?? "#";
|
|
454
|
+
};
|
|
405
455
|
return _$createComponent(CalendarNavigationLink, {
|
|
406
456
|
get owner() {
|
|
407
457
|
return props.owner;
|
|
@@ -430,12 +480,28 @@ const CalendarViewLinks = props => {
|
|
|
430
480
|
})));
|
|
431
481
|
};
|
|
432
482
|
|
|
483
|
+
/**
|
|
484
|
+
* The same day some months later, or the last day of that month when it is shorter: a step from January 31 lands on
|
|
485
|
+
* February 28, never in March. stdlib's `addMonths` runs over into the next month.
|
|
486
|
+
*/
|
|
487
|
+
const addMonthsClamped = (date, months, dateConfig) => {
|
|
488
|
+
const moved = calendar.addMonths(date, months, dateConfig);
|
|
489
|
+
if (calendar.formatDateKey(moved, dateConfig).slice(8) === calendar.formatDateKey(date, dateConfig).slice(8)) return moved;
|
|
490
|
+
return calendar.addDays(calendar.startOfMonth(moved, dateConfig), -1, dateConfig);
|
|
491
|
+
};
|
|
492
|
+
|
|
433
493
|
/** Custom views page by day, like the day view. */
|
|
434
494
|
const adjacentCalendarDate = (date, view, direction, dateConfig) => {
|
|
435
|
-
if (view === "year") return
|
|
436
|
-
if (view === "month" || view === "mobile-month") return
|
|
495
|
+
if (view === "year") return addMonthsClamped(date, direction * 12, dateConfig);
|
|
496
|
+
if (view === "month" || view === "mobile-month") return addMonthsClamped(date, direction, dateConfig);
|
|
437
497
|
return calendar.addDays(date, direction * (view === "week" ? 7 : 1), dateConfig);
|
|
438
498
|
};
|
|
499
|
+
|
|
500
|
+
/** Opens a view at a day through the host's callbacks. Hosts may keep the view and the date apart, so both hear it. */
|
|
501
|
+
const openViewAt = (owner, date, view) => {
|
|
502
|
+
owner.onViewChange?.(view);
|
|
503
|
+
owner.onDateChange?.(date, view);
|
|
504
|
+
};
|
|
439
505
|
const CalendarHeader = props => {
|
|
440
506
|
const messages = useUiMessages();
|
|
441
507
|
const dateConfig = () => ownerDateConfig(props.owner);
|
|
@@ -456,8 +522,51 @@ const CalendarHeader = props => {
|
|
|
456
522
|
if (props.view === "week") return `${formatDay(calendar.startOfWeek(props.date, dateConfig()), dateConfig())} - ${formatDay(calendar.addDays(calendar.startOfWeek(props.date, dateConfig()), 6, dateConfig()), dateConfig())}`;
|
|
457
523
|
return formatMonth(props.date, dateConfig());
|
|
458
524
|
};
|
|
525
|
+
/**
|
|
526
|
+
* The titles this view can show around the current date: the months of the year, the weeks of the month, or a day
|
|
527
|
+
* of each weekday in every month. Their widest one sets the title's width.
|
|
528
|
+
*/
|
|
529
|
+
const titleWidths = createMemo(() => {
|
|
530
|
+
const context = dateConfig();
|
|
531
|
+
if (props.view === "year") return [];
|
|
532
|
+
const {
|
|
533
|
+
year
|
|
534
|
+
} = zonedYearMonth(props.date, context);
|
|
535
|
+
if (props.view === "month" || props.view === "mobile-month") return Array.from({
|
|
536
|
+
length: 12
|
|
537
|
+
}, (_, month) => formatMonth(zonedMonthDate(year, month, context), context));
|
|
538
|
+
if (props.view === "week") {
|
|
539
|
+
const first = calendar.startOfWeek(calendar.startOfMonth(props.date, context), context);
|
|
540
|
+
return Array.from({
|
|
541
|
+
length: 6
|
|
542
|
+
}, (_, week) => {
|
|
543
|
+
const start = calendar.addDays(first, week * 7, context);
|
|
544
|
+
return `${formatDay(start, context)} - ${formatDay(calendar.addDays(start, 6, context), context)}`;
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return Array.from({
|
|
548
|
+
length: 12
|
|
549
|
+
}, (_, month) => month).flatMap(month => {
|
|
550
|
+
const first = zonedMonthDate(year, month, context);
|
|
551
|
+
return Array.from({
|
|
552
|
+
length: 7
|
|
553
|
+
}, (_, offset) => calendar.addDays(first, 21 + offset, context).toLocaleDateString(context.locale ?? "en", {
|
|
554
|
+
weekday: "long",
|
|
555
|
+
month: "long",
|
|
556
|
+
day: "numeric",
|
|
557
|
+
year: "numeric",
|
|
558
|
+
timeZone: context.timeZone
|
|
559
|
+
}));
|
|
560
|
+
});
|
|
561
|
+
});
|
|
459
562
|
const goDate = date => props.owner.onDateChange?.(date, props.view);
|
|
460
563
|
const goView = view => {
|
|
564
|
+
// Like the switcher's links, the other views open at the day selected in the month view.
|
|
565
|
+
const day = props.focusDay;
|
|
566
|
+
if (day && props.owner.onDateChange && view !== props.view) {
|
|
567
|
+
openViewAt(props.owner, day, view);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
461
570
|
if (props.owner.onViewChange) {
|
|
462
571
|
props.owner.onViewChange(view);
|
|
463
572
|
return;
|
|
@@ -478,9 +587,9 @@ const CalendarHeader = props => {
|
|
|
478
587
|
class: "k2b-calendar-header__nav-button"
|
|
479
588
|
},
|
|
480
589
|
get children() {
|
|
481
|
-
return _$ssr(_tmpl$
|
|
590
|
+
return _$ssr(_tmpl$10, `ti ${_$escape(icon, true)}`);
|
|
482
591
|
}
|
|
483
|
-
}) : props.owner.onDateChange ? _$ssr(_tmpl$
|
|
592
|
+
}) : props.owner.onDateChange ? _$ssr(_tmpl$11, _$ssrAttribute("aria-label", _$escape(label, true), false), `ti ${_$escape(icon, true)}`) : _$createComponent(CalendarNavigationLink, {
|
|
484
593
|
get owner() {
|
|
485
594
|
return props.owner;
|
|
486
595
|
},
|
|
@@ -491,14 +600,14 @@ const CalendarHeader = props => {
|
|
|
491
600
|
class: "k2b-calendar-header__nav-button"
|
|
492
601
|
},
|
|
493
602
|
get children() {
|
|
494
|
-
return _$ssr(_tmpl$
|
|
603
|
+
return _$ssr(_tmpl$10, `ti ${_$escape(icon, true)}`);
|
|
495
604
|
}
|
|
496
605
|
});
|
|
497
606
|
};
|
|
498
607
|
const todayButton = () => {
|
|
499
608
|
const today = calendar.today(dateConfig());
|
|
500
609
|
const href = props.owner.getDateHref?.(today, props.view);
|
|
501
|
-
const label = _$ssr(_tmpl$
|
|
610
|
+
const label = _$ssr(_tmpl$12, _$escape(props.labels.today));
|
|
502
611
|
return (props.owner.onNavigateHref || props.owner.onNavigate) && href ? _$createComponent(CalendarNavigationLink, {
|
|
503
612
|
get owner() {
|
|
504
613
|
return props.owner;
|
|
@@ -545,7 +654,19 @@ const CalendarHeader = props => {
|
|
|
545
654
|
value: "year",
|
|
546
655
|
label: props.labels.year
|
|
547
656
|
}].filter(option => !props.owner.views || props.owner.views.includes(option.value)), ...(props.owner.customViews ?? [])]);
|
|
548
|
-
return _$ssr(_tmpl$
|
|
657
|
+
return _$ssr(_tmpl$15, _$escape(navButton(previous(), "ti-chevron-left", props.labels.previous)), _$escape(navButton(next(), "ti-chevron-right", props.labels.next)), _$escape(title()), _$escape(_$createComponent(For, {
|
|
658
|
+
get each() {
|
|
659
|
+
return titleWidths();
|
|
660
|
+
},
|
|
661
|
+
children: candidate => _$ssr(_tmpl$16, _$escape(candidate))
|
|
662
|
+
})), _$escape(_$createComponent(Show, {
|
|
663
|
+
get when() {
|
|
664
|
+
return props.owner.toolbarContent;
|
|
665
|
+
},
|
|
666
|
+
get children() {
|
|
667
|
+
return _$ssr(_tmpl$13, _$escape(props.owner.toolbarContent));
|
|
668
|
+
}
|
|
669
|
+
})), _$escape(todayButton()), _$escape(_$createComponent(Show, {
|
|
549
670
|
get when() {
|
|
550
671
|
return _$memo(() => !!!props.owner.onViewChange)() && props.owner.getViewHref;
|
|
551
672
|
},
|
|
@@ -571,6 +692,9 @@ const CalendarHeader = props => {
|
|
|
571
692
|
},
|
|
572
693
|
get options() {
|
|
573
694
|
return viewOptions();
|
|
695
|
+
},
|
|
696
|
+
get focusDay() {
|
|
697
|
+
return props.focusDay;
|
|
574
698
|
}
|
|
575
699
|
});
|
|
576
700
|
}
|
|
@@ -579,12 +703,15 @@ const CalendarHeader = props => {
|
|
|
579
703
|
return props.owner.navigationPending;
|
|
580
704
|
},
|
|
581
705
|
get children() {
|
|
582
|
-
return _$ssr(_tmpl$
|
|
706
|
+
return _$ssr(_tmpl$14);
|
|
583
707
|
}
|
|
584
708
|
})));
|
|
585
709
|
};
|
|
586
|
-
|
|
710
|
+
|
|
711
|
+
/** The compact month of `mobile-month`: a picker whose days link to their agenda. */
|
|
712
|
+
const MonthPickerView = props => {
|
|
587
713
|
const messages = useUiMessages();
|
|
714
|
+
const calendarMessages = useCalendarMessages();
|
|
588
715
|
const dateConfig = createMemo(() => ownerDateConfig(props.owner));
|
|
589
716
|
const [movePreview, setMovePreview] = createSignal(null);
|
|
590
717
|
const [movingEventId, setMovingEventId] = createSignal("");
|
|
@@ -637,28 +764,28 @@ const MonthView = props => {
|
|
|
637
764
|
});
|
|
638
765
|
};
|
|
639
766
|
onCleanup(() => cancelInteraction?.());
|
|
640
|
-
return _$ssr(_tmpl$
|
|
767
|
+
return _$ssr(_tmpl$18, _$ssrStyleProperty("grid-template-rows:", `auto repeat(${_$escape(weeks().length, true)}, ${props.compact ? "minmax(2.75rem, auto)" : "minmax(5rem, 1fr)"})`), _$ssrAttribute("data-week-numbers", props.owner.withWeekNumbers ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
641
768
|
get when() {
|
|
642
769
|
return props.owner.withWeekNumbers;
|
|
643
770
|
},
|
|
644
771
|
get children() {
|
|
645
|
-
return _$ssr(_tmpl$
|
|
772
|
+
return _$ssr(_tmpl$17, _$escape(messages().weekShort));
|
|
646
773
|
}
|
|
647
774
|
})), _$escape(_$createComponent(For, {
|
|
648
775
|
get each() {
|
|
649
776
|
return weekdays();
|
|
650
777
|
},
|
|
651
|
-
children: day => _$ssr(_tmpl$
|
|
778
|
+
children: day => _$ssr(_tmpl$17, _$escape(day))
|
|
652
779
|
})), _$escape(_$createComponent(For, {
|
|
653
780
|
get each() {
|
|
654
781
|
return weeks();
|
|
655
782
|
},
|
|
656
|
-
children: week => _$ssr(_tmpl$
|
|
783
|
+
children: week => _$ssr(_tmpl$20, _$ssrAttribute("data-week-numbers", props.owner.withWeekNumbers ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
657
784
|
get when() {
|
|
658
785
|
return props.owner.withWeekNumbers;
|
|
659
786
|
},
|
|
660
787
|
get children() {
|
|
661
|
-
return _$ssr(_tmpl$
|
|
788
|
+
return _$ssr(_tmpl$19, _$escape(zonedWeekNumber(week[0], dateConfig())));
|
|
662
789
|
}
|
|
663
790
|
})), _$escape(_$createComponent(For, {
|
|
664
791
|
each: week,
|
|
@@ -687,7 +814,7 @@ const MonthView = props => {
|
|
|
687
814
|
hiddenCount: Math.max(0, candidates.length - visibleLimit)
|
|
688
815
|
};
|
|
689
816
|
});
|
|
690
|
-
return _$ssr(_tmpl$
|
|
817
|
+
return _$ssr(_tmpl$22, _$ssrAttribute("data-calendar-day-key", _$escape(dayKey, true), false) + _$ssrAttribute("data-outside", !sameMonth ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-drop-preview", movePreview()?.start && calendar.formatDateKey(movePreview().start, dateConfig()) === dayKey ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
691
818
|
get when() {
|
|
692
819
|
return props.owner.onSlotActivate;
|
|
693
820
|
},
|
|
@@ -736,7 +863,7 @@ const MonthView = props => {
|
|
|
736
863
|
return props.owner.onSlotActivate && href;
|
|
737
864
|
},
|
|
738
865
|
get fallback() {
|
|
739
|
-
return _$ssr(_tmpl$
|
|
866
|
+
return _$ssr(_tmpl$23, _$ssrAttribute("data-today", isToday ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-outside", !sameMonth ? "true" : _$escape(undefined, true), false), _$escape(calendar.formatDayNumber(day, dateConfig())));
|
|
740
867
|
},
|
|
741
868
|
children: dayHref => _$createComponent(CalendarNavigationLink, {
|
|
742
869
|
get owner() {
|
|
@@ -761,18 +888,18 @@ const MonthView = props => {
|
|
|
761
888
|
})
|
|
762
889
|
})), _$escape(_$createComponent(Show, {
|
|
763
890
|
when: dayBadge,
|
|
764
|
-
children: badge => _$ssr(_tmpl$
|
|
891
|
+
children: badge => _$ssr(_tmpl$24, _$escape(_$createComponent(Show, {
|
|
765
892
|
get when() {
|
|
766
893
|
return badge().icon;
|
|
767
894
|
},
|
|
768
|
-
children: icon => _$ssr(_tmpl$
|
|
895
|
+
children: icon => _$ssr(_tmpl$10, `k2b-calendar-month__badge-icon ti ti-${_$escape(icon(), true)}`)
|
|
769
896
|
})), _$escape(badge().label))
|
|
770
897
|
})), _$escape(_$createComponent(Show, {
|
|
771
898
|
get when() {
|
|
772
899
|
return eventStack().preview;
|
|
773
900
|
},
|
|
774
901
|
get children() {
|
|
775
|
-
return _$ssr(_tmpl$
|
|
902
|
+
return _$ssr(_tmpl$21, _$escape(props.events.find(event => event.id === eventStack().preview.id)?.title ?? messages().moveEvent));
|
|
776
903
|
}
|
|
777
904
|
})), _$escape(_$createComponent(For, {
|
|
778
905
|
get each() {
|
|
@@ -808,7 +935,9 @@ const MonthView = props => {
|
|
|
808
935
|
class: "k2b-calendar-month__more"
|
|
809
936
|
},
|
|
810
937
|
get children() {
|
|
811
|
-
return
|
|
938
|
+
return calendarMessages().calendarMoreEvents({
|
|
939
|
+
count: eventStack().hiddenCount
|
|
940
|
+
});
|
|
812
941
|
}
|
|
813
942
|
});
|
|
814
943
|
}
|
|
@@ -816,7 +945,7 @@ const MonthView = props => {
|
|
|
816
945
|
get each() {
|
|
817
946
|
return events.slice(0, 4);
|
|
818
947
|
},
|
|
819
|
-
children: event => _$ssr(_tmpl$
|
|
948
|
+
children: event => _$ssr(_tmpl$25, _$ssrAttribute("data-color", event.colorHex ? _$escape(undefined, true) : _$escape(event.color ?? "blue", true), false), _$ssrStyle(event.colorHex ? {
|
|
820
949
|
"background-color": event.colorHex
|
|
821
950
|
} : undefined))
|
|
822
951
|
})));
|
|
@@ -824,6 +953,989 @@ const MonthView = props => {
|
|
|
824
953
|
})))
|
|
825
954
|
})));
|
|
826
955
|
};
|
|
956
|
+
|
|
957
|
+
/** One entry of the month grid: its whole range, and the days it covers as `YYYY-MM-DD` keys. */
|
|
958
|
+
|
|
959
|
+
/** Lane height and gap of the month entries, in rem; the stylesheet reads both from the grid's custom properties. */
|
|
960
|
+
const MONTH_LANE_REM = 1.25;
|
|
961
|
+
const MONTH_LANE_GAP_REM = 0.125;
|
|
962
|
+
/** Rows drawn before the browser has measured the cells, as in the server response. */
|
|
963
|
+
const MONTH_DEFAULT_LANES = 3;
|
|
964
|
+
/** Room a bar keeps beside its title and date hint: padding, torn edge, gap, and an icon such as a task box or a flag. */
|
|
965
|
+
const MONTH_HINT_SLACK_REM = 2.25;
|
|
966
|
+
const dayKeyNumber = key => {
|
|
967
|
+
const [year = "1970", month = "1", day = "1"] = key.split("-");
|
|
968
|
+
return Date.UTC(Number(year), Number(month) - 1, Number(day)) / 86_400_000;
|
|
969
|
+
};
|
|
970
|
+
const orderedSelection = (anchor, focus) => anchor <= focus ? {
|
|
971
|
+
first: anchor,
|
|
972
|
+
last: focus
|
|
973
|
+
} : {
|
|
974
|
+
first: focus,
|
|
975
|
+
last: anchor
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
/** Groups the per-day pieces of the events back into one entry each: bars first, then the others by time. */
|
|
979
|
+
const monthItems = events => {
|
|
980
|
+
const items = new Map();
|
|
981
|
+
for (const piece of events) {
|
|
982
|
+
const key = `${piece.id}\u0000${piece.sourceStartDate.getTime()}`;
|
|
983
|
+
const item = items.get(key);
|
|
984
|
+
if (item) {
|
|
985
|
+
if (piece.dayKey < item.firstKey) item.firstKey = piece.dayKey;
|
|
986
|
+
if (piece.dayKey > item.lastKey) item.lastKey = piece.dayKey;
|
|
987
|
+
item.days.set(piece.dayKey, piece);
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
items.set(key, {
|
|
991
|
+
key,
|
|
992
|
+
event: {
|
|
993
|
+
...piece,
|
|
994
|
+
startDate: piece.sourceStartDate,
|
|
995
|
+
endDate: piece.sourceEndDate
|
|
996
|
+
},
|
|
997
|
+
firstKey: piece.dayKey,
|
|
998
|
+
lastKey: piece.dayKey,
|
|
999
|
+
bar: false,
|
|
1000
|
+
days: new Map([[piece.dayKey, piece]])
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
for (const item of items.values()) item.bar = item.firstKey !== item.lastKey || Boolean(item.event.allDay && item.event.display !== "marker");
|
|
1004
|
+
return [...items.values()].sort((left, right) => Number(right.bar) - Number(left.bar) || left.event.sourceStartDate.getTime() - right.event.sourceStartDate.getTime() || left.event.title.localeCompare(right.event.title));
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
/** Places a calendar popover below its anchor when there is room and above it otherwise, inside the viewport. */
|
|
1008
|
+
const placeCalendarPopover = (anchor, popover) => {
|
|
1009
|
+
const margin = 8;
|
|
1010
|
+
const gap = 4;
|
|
1011
|
+
const rect = anchor.getBoundingClientRect();
|
|
1012
|
+
const width = Math.min(popover.offsetWidth || 288, window.innerWidth - margin * 2);
|
|
1013
|
+
const height = popover.offsetHeight;
|
|
1014
|
+
const left = Math.max(margin, Math.min(rect.left, window.innerWidth - width - margin));
|
|
1015
|
+
const roomBelow = window.innerHeight - rect.bottom - gap - margin;
|
|
1016
|
+
const above = height > roomBelow && rect.top - gap - margin > roomBelow;
|
|
1017
|
+
const top = above ? Math.max(margin, rect.top - height - gap) : Math.min(rect.bottom + gap, window.innerHeight - height - margin);
|
|
1018
|
+
popover.style.left = `${left}px`;
|
|
1019
|
+
popover.style.top = `${Math.max(margin, top)}px`;
|
|
1020
|
+
};
|
|
1021
|
+
let measureContext;
|
|
1022
|
+
/** The width of a text in a font, from a canvas, so a bar can tell whether its title leaves room for a date hint. */
|
|
1023
|
+
const textWidth = (text, font) => {
|
|
1024
|
+
measureContext ??= typeof document === "undefined" ? null : document.createElement("canvas").getContext("2d");
|
|
1025
|
+
if (!measureContext) return Number.POSITIVE_INFINITY;
|
|
1026
|
+
measureContext.font = font;
|
|
1027
|
+
return measureContext.measureText(text).width;
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
/** Follows a calendar link the way the calendar's own links do: through the host's navigation when it has one. */
|
|
1031
|
+
const followCalendarHref = (owner, href) => {
|
|
1032
|
+
if (owner.onNavigateHref) owner.onNavigateHref(href);else window.location.assign(href);
|
|
1033
|
+
};
|
|
1034
|
+
const PRINTABLE_KEY = /^.$/u;
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* The month grid. A click selects a day and never navigates, a drag or Shift selects a range, and the arrow keys move
|
|
1038
|
+
* the selection. The quick create opens at the selection: quietly after a click, with the focus after a drag, a
|
|
1039
|
+
* double-click, Enter, or N. Every event is one bar per week row in a lane it keeps on each of its days, and each
|
|
1040
|
+
* cell shows as many entries as its height holds.
|
|
1041
|
+
*/
|
|
1042
|
+
const MonthView = props => {
|
|
1043
|
+
const messages = useUiMessages();
|
|
1044
|
+
const calendarMessages = useCalendarMessages();
|
|
1045
|
+
const dateConfig = createMemo(() => ownerDateConfig(props.owner));
|
|
1046
|
+
// The shown month and its grid change only with the month, so a refresh of the same month keeps every cell and its
|
|
1047
|
+
// focus.
|
|
1048
|
+
const month = createMemo(() => zonedYearMonth(props.date, dateConfig()), undefined, {
|
|
1049
|
+
equals: (previous, next) => previous.year === next.year && previous.month === next.month
|
|
1050
|
+
});
|
|
1051
|
+
const weeks = createMemo(() => calendar.getMonthGrid(month().year, month().month, dateConfig()).map(week => week.map(day => ({
|
|
1052
|
+
day,
|
|
1053
|
+
key: calendar.formatDateKey(day, dateConfig())
|
|
1054
|
+
}))), undefined, {
|
|
1055
|
+
equals: (previous, next) => previous.map(week => week[0]?.key).join() === next.map(week => week[0]?.key).join()
|
|
1056
|
+
});
|
|
1057
|
+
const weekdays = createMemo(() => calendar.weekdays(dateConfig()));
|
|
1058
|
+
const todayKey = createMemo(() => calendar.formatDateKey(props.now, dateConfig()));
|
|
1059
|
+
const items = createMemo(() => monthItems(props.events));
|
|
1060
|
+
const weekLayouts = createMemo(() => weeks().map(week => layoutMonthWeek(week.map(entry => entry.key), items().map(item => ({
|
|
1061
|
+
item,
|
|
1062
|
+
firstKey: item.firstKey,
|
|
1063
|
+
lastKey: item.lastKey,
|
|
1064
|
+
bar: item.bar
|
|
1065
|
+
})))));
|
|
1066
|
+
const [capacities, setCapacities] = createSignal([]);
|
|
1067
|
+
const [cellWidth, setCellWidth] = createSignal(0);
|
|
1068
|
+
const [barFont, setBarFont] = createSignal("");
|
|
1069
|
+
const [anchorKey, setAnchorKey] = createSignal(null);
|
|
1070
|
+
const [focusKey, setFocusKey] = createSignal(null);
|
|
1071
|
+
const [popover, setPopover] = createSignal(null);
|
|
1072
|
+
/** Whether the quick create holds the focus; until then the grid keeps it. */
|
|
1073
|
+
const [createActive, setCreateActive] = createSignal(false);
|
|
1074
|
+
const [linkedItem, setLinkedItem] = createSignal("");
|
|
1075
|
+
const [movePreview, setMovePreview] = createSignal(null);
|
|
1076
|
+
const [movingEventId, setMovingEventId] = createSignal("");
|
|
1077
|
+
const cells = new Map();
|
|
1078
|
+
let popoverElement;
|
|
1079
|
+
let cancelInteraction;
|
|
1080
|
+
let suppressClickUntil = 0;
|
|
1081
|
+
let lastPointer = "mouse";
|
|
1082
|
+
let menuKey = null;
|
|
1083
|
+
/** The day to select once paging with Page Up or Page Down has shown its month. */
|
|
1084
|
+
let pendingKey = null;
|
|
1085
|
+
const selection = createMemo(() => {
|
|
1086
|
+
const anchor = anchorKey();
|
|
1087
|
+
const focus = focusKey();
|
|
1088
|
+
return anchor && focus ? orderedSelection(anchor, focus) : null;
|
|
1089
|
+
});
|
|
1090
|
+
const parseKey = key => calendar.parseCalendarDate(key, dateConfig());
|
|
1091
|
+
const rangeOf = range => ({
|
|
1092
|
+
start: parseKey(range.first),
|
|
1093
|
+
end: calendar.addDays(parseKey(range.last), 1, dateConfig()),
|
|
1094
|
+
allDay: true
|
|
1095
|
+
});
|
|
1096
|
+
const visibleKeys = createMemo(() => weeks().flatMap(week => week.map(entry => entry.key)));
|
|
1097
|
+
const rovingKey = createMemo(() => {
|
|
1098
|
+
const keys = visibleKeys();
|
|
1099
|
+
const focus = focusKey();
|
|
1100
|
+
if (focus && keys.includes(focus)) return focus;
|
|
1101
|
+
if (keys.includes(todayKey())) return todayKey();
|
|
1102
|
+
return calendar.formatDateKey(zonedMonthDate(month().year, month().month, dateConfig()), dateConfig());
|
|
1103
|
+
});
|
|
1104
|
+
const dropRange = createMemo(() => {
|
|
1105
|
+
const preview = movePreview();
|
|
1106
|
+
if (!preview) return null;
|
|
1107
|
+
const keys = previewSegments(preview, visibleKeys().map(parseKey), dateConfig()).map(piece => piece.dayKey);
|
|
1108
|
+
if (keys.length === 0) return null;
|
|
1109
|
+
keys.sort();
|
|
1110
|
+
return {
|
|
1111
|
+
first: keys[0],
|
|
1112
|
+
last: keys[keys.length - 1]
|
|
1113
|
+
};
|
|
1114
|
+
});
|
|
1115
|
+
const within = (range, key) => Boolean(range && key >= range.first && key <= range.last);
|
|
1116
|
+
/** Where a day sits in the selection: alone, at its start or end, or in between. */
|
|
1117
|
+
const selectionPart = key => {
|
|
1118
|
+
const range = selection();
|
|
1119
|
+
if (!range || !within(range, key)) return undefined;
|
|
1120
|
+
if (range.first === range.last) return "single";
|
|
1121
|
+
return key === range.first ? "start" : key === range.last ? "end" : "middle";
|
|
1122
|
+
};
|
|
1123
|
+
const select = (anchor, focus = anchor) => {
|
|
1124
|
+
setAnchorKey(anchor);
|
|
1125
|
+
setFocusKey(focus);
|
|
1126
|
+
};
|
|
1127
|
+
// The host learns every change of the selected days, and a new month starts without a selection. The selection
|
|
1128
|
+
// goes with the view, so switching to another view reports that nothing is selected.
|
|
1129
|
+
createEffect(on(selection, range => props.owner.onSelectionChange?.(range ? rangeOf(range) : null), {
|
|
1130
|
+
defer: true
|
|
1131
|
+
}));
|
|
1132
|
+
onCleanup(() => {
|
|
1133
|
+
if (untrack(selection)) props.owner.onSelectionChange?.(null);
|
|
1134
|
+
});
|
|
1135
|
+
createEffect(on(selection, range => props.onFocusDay?.(range ? parseKey(range.first) : null)));
|
|
1136
|
+
createEffect(on(month, () => {
|
|
1137
|
+
setPopover(null);
|
|
1138
|
+
const key = pendingKey;
|
|
1139
|
+
pendingKey = null;
|
|
1140
|
+
if (key && visibleKeys().includes(key)) {
|
|
1141
|
+
select(key);
|
|
1142
|
+
queueMicrotask(() => focusCell(key));
|
|
1143
|
+
} else select(null);
|
|
1144
|
+
}, {
|
|
1145
|
+
defer: true
|
|
1146
|
+
}));
|
|
1147
|
+
const dayLabel = day => day.toLocaleDateString(dateConfig().locale ?? "en", {
|
|
1148
|
+
weekday: "long",
|
|
1149
|
+
year: "numeric",
|
|
1150
|
+
month: "long",
|
|
1151
|
+
day: "numeric",
|
|
1152
|
+
timeZone: dateConfig().timeZone
|
|
1153
|
+
});
|
|
1154
|
+
const shortDayLabel = (key, withMonth = true) => parseKey(key).toLocaleDateString(dateConfig().locale ?? "en", {
|
|
1155
|
+
day: "numeric",
|
|
1156
|
+
...(withMonth ? {
|
|
1157
|
+
month: "short"
|
|
1158
|
+
} : {}),
|
|
1159
|
+
timeZone: dateConfig().timeZone
|
|
1160
|
+
});
|
|
1161
|
+
const monthDayLabel = (date, withTime) => date.toLocaleString(dateConfig().locale ?? "en", {
|
|
1162
|
+
month: "long",
|
|
1163
|
+
day: "numeric",
|
|
1164
|
+
...(withTime ? {
|
|
1165
|
+
hour: "2-digit",
|
|
1166
|
+
minute: "2-digit"
|
|
1167
|
+
} : {}),
|
|
1168
|
+
timeZone: dateConfig().timeZone
|
|
1169
|
+
});
|
|
1170
|
+
/** A long event names the days it spans; a one-day event keeps the chip's own title and time. */
|
|
1171
|
+
const itemLabel = item => {
|
|
1172
|
+
if (item.firstKey === item.lastKey) return undefined;
|
|
1173
|
+
const allDay = Boolean(item.event.allDay);
|
|
1174
|
+
return messages().calendarEventTime({
|
|
1175
|
+
title: item.event.title,
|
|
1176
|
+
start: monthDayLabel(allDay ? parseKey(item.firstKey) : item.event.sourceStartDate, !allDay),
|
|
1177
|
+
end: monthDayLabel(allDay ? parseKey(item.lastKey) : item.event.sourceEndDate, !allDay)
|
|
1178
|
+
});
|
|
1179
|
+
};
|
|
1180
|
+
/** The start time before the title of a one-day entry; bars and all-day entries show none. */
|
|
1181
|
+
const leadingTime = item => item.bar || item.event.allDay ? undefined : formatTime(item.event.sourceStartDate, dateConfig());
|
|
1182
|
+
/** The day at a torn end of a bar, with its month when that day lies outside the shown month. */
|
|
1183
|
+
const hintDate = target => ({
|
|
1184
|
+
day: Number(target.slice(8, 10)),
|
|
1185
|
+
month: Number(target.slice(0, 4)) === month().year && Number(target.slice(5, 7)) === month().month + 1 ? undefined : parseKey(target).toLocaleDateString(dateConfig().locale ?? "en", {
|
|
1186
|
+
month: "short",
|
|
1187
|
+
timeZone: dateConfig().timeZone
|
|
1188
|
+
}).replace(/\.$/, "")
|
|
1189
|
+
});
|
|
1190
|
+
const selectionHeading = range => {
|
|
1191
|
+
if (range.first === range.last) return parseKey(range.first).toLocaleDateString(dateConfig().locale ?? "en", {
|
|
1192
|
+
weekday: "long",
|
|
1193
|
+
month: "long",
|
|
1194
|
+
day: "numeric",
|
|
1195
|
+
timeZone: dateConfig().timeZone
|
|
1196
|
+
});
|
|
1197
|
+
const days = dayKeyNumber(range.last) - dayKeyNumber(range.first) + 1;
|
|
1198
|
+
return calendarMessages().calendarSelectedDays({
|
|
1199
|
+
start: shortDayLabel(range.first),
|
|
1200
|
+
end: shortDayLabel(range.last),
|
|
1201
|
+
count: days
|
|
1202
|
+
});
|
|
1203
|
+
};
|
|
1204
|
+
const viewAvailable = view => !props.owner.views || props.owner.views.includes(view);
|
|
1205
|
+
const viewHref = (key, view) => viewAvailable(view) ? props.owner.getDateHref?.(parseKey(key), view) : undefined;
|
|
1206
|
+
/** Whether the calendar can open another view at a day: through a link, or through the host's date change. */
|
|
1207
|
+
const canOpenView = view => viewAvailable(view) && Boolean(props.owner.getDateHref || props.owner.onDateChange);
|
|
1208
|
+
const openView = (key, view) => {
|
|
1209
|
+
const href = viewHref(key, view);
|
|
1210
|
+
if (href) followCalendarHref(props.owner, href);else openViewAt(props.owner, parseKey(key), view);
|
|
1211
|
+
};
|
|
1212
|
+
/** The calendar's own entries of the day menu, Open day and Open week, need no application entries. */
|
|
1213
|
+
const hasMenu = () => Boolean(props.owner.selectionMenu) || canOpenView("day") || canOpenView("week");
|
|
1214
|
+
|
|
1215
|
+
/** The day cell under a point, from the cells' boxes: a bar lies over days that are not its parent. */
|
|
1216
|
+
const dayKeyAtPoint = (clientX, clientY) => {
|
|
1217
|
+
for (const [key, cell] of cells) {
|
|
1218
|
+
if (!cell.isConnected) continue;
|
|
1219
|
+
const rect = cell.getBoundingClientRect();
|
|
1220
|
+
if (clientX >= rect.left && clientX < rect.right && clientY >= rect.top && clientY < rect.bottom) return key;
|
|
1221
|
+
}
|
|
1222
|
+
return null;
|
|
1223
|
+
};
|
|
1224
|
+
const focusCell = key => cells.get(key)?.focus({
|
|
1225
|
+
preventScroll: true
|
|
1226
|
+
});
|
|
1227
|
+
const closePopover = (restoreFocus = true) => {
|
|
1228
|
+
if (!popover()) return;
|
|
1229
|
+
setPopover(null);
|
|
1230
|
+
setCreateActive(false);
|
|
1231
|
+
if (restoreFocus) focusCell(rovingKey());
|
|
1232
|
+
};
|
|
1233
|
+
const popoverFocusable = () => popoverElement?.querySelector("input,textarea,select,button,a[href],[tabindex]:not([tabindex='-1'])") ?? null;
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Opens the quick create at the selection. Quietly, it only shows beside the grid, which keeps the focus until the
|
|
1237
|
+
* person types or tabs into it; otherwise it takes the focus at once.
|
|
1238
|
+
*/
|
|
1239
|
+
const openCreate = (range, options) => {
|
|
1240
|
+
if (!props.owner.renderQuickCreate) return false;
|
|
1241
|
+
const anchor = cells.get(focusKey() && within(range, focusKey()) ? focusKey() : range.last);
|
|
1242
|
+
if (!anchor) return false;
|
|
1243
|
+
setCreateActive(options.active);
|
|
1244
|
+
setPopover({
|
|
1245
|
+
kind: "create",
|
|
1246
|
+
range,
|
|
1247
|
+
anchor,
|
|
1248
|
+
create: options.create
|
|
1249
|
+
});
|
|
1250
|
+
return true;
|
|
1251
|
+
};
|
|
1252
|
+
const activateCreate = () => {
|
|
1253
|
+
if (popover()?.kind !== "create") return false;
|
|
1254
|
+
setCreateActive(true);
|
|
1255
|
+
popoverFocusable()?.focus();
|
|
1256
|
+
return true;
|
|
1257
|
+
};
|
|
1258
|
+
/** Enter, a double-click, and N create on the selection, with the quick create or the host's own create action. */
|
|
1259
|
+
const createOnSelection = () => {
|
|
1260
|
+
const range = selection();
|
|
1261
|
+
if (!range) return;
|
|
1262
|
+
if (openCreate(range, {
|
|
1263
|
+
active: true
|
|
1264
|
+
})) return;
|
|
1265
|
+
if (props.owner.onSlotActivate) props.owner.onSlotActivate(rangeOf(range));
|
|
1266
|
+
};
|
|
1267
|
+
const openDayList = key => {
|
|
1268
|
+
const anchor = cells.get(key);
|
|
1269
|
+
if (!anchor) return;
|
|
1270
|
+
setCreateActive(true);
|
|
1271
|
+
setPopover({
|
|
1272
|
+
kind: "day",
|
|
1273
|
+
dayKey: key,
|
|
1274
|
+
anchor
|
|
1275
|
+
});
|
|
1276
|
+
};
|
|
1277
|
+
/** A quiet quick create follows the selection; any other popover closes when the selection changes. */
|
|
1278
|
+
const followSelection = () => {
|
|
1279
|
+
const current = popover();
|
|
1280
|
+
const range = selection();
|
|
1281
|
+
if (!current) return;
|
|
1282
|
+
if (current.kind === "create" && !createActive() && range) openCreate(range, {
|
|
1283
|
+
active: false
|
|
1284
|
+
});else closePopover(false);
|
|
1285
|
+
};
|
|
1286
|
+
const pageTo = target => {
|
|
1287
|
+
const date = parseKey(target);
|
|
1288
|
+
const href = props.owner.getDateHref?.(date, "month");
|
|
1289
|
+
// Only a month that is on its way may select the day, so a host that cannot page never leaves one behind.
|
|
1290
|
+
if (props.owner.onDateChange) {
|
|
1291
|
+
pendingKey = target;
|
|
1292
|
+
props.owner.onDateChange(date, "month");
|
|
1293
|
+
} else if (href) {
|
|
1294
|
+
pendingKey = target;
|
|
1295
|
+
followCalendarHref(props.owner, href);
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
const moveFocus = (key, extend) => {
|
|
1299
|
+
if (!visibleKeys().includes(key)) {
|
|
1300
|
+
pageTo(key);
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
if (extend) select(anchorKey() ?? focusKey() ?? key, key);else select(key);
|
|
1304
|
+
focusCell(key);
|
|
1305
|
+
followSelection();
|
|
1306
|
+
};
|
|
1307
|
+
const onCellKeyDown = (event, key) => {
|
|
1308
|
+
if (event.target !== event.currentTarget || event.ctrlKey || event.metaKey || event.altKey) return;
|
|
1309
|
+
const current = popover();
|
|
1310
|
+
const quiet = current?.kind === "create" && !createActive();
|
|
1311
|
+
// The quiet quick create takes over once the person types or tabs into it. Space stays the grid's key for the day
|
|
1312
|
+
// list; N is a title's first letter like any other, as the quick create it would open is already there.
|
|
1313
|
+
if (quiet && (event.key === "Tab" && !event.shiftKey || PRINTABLE_KEY.test(event.key) && event.key !== " ")) {
|
|
1314
|
+
const field = popoverFocusable();
|
|
1315
|
+
if (field) {
|
|
1316
|
+
if (event.key === "Tab") event.preventDefault();
|
|
1317
|
+
setCreateActive(true);
|
|
1318
|
+
field.focus();
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
const day = parseKey(key);
|
|
1323
|
+
const shift = days => calendar.formatDateKey(calendar.addDays(day, days, dateConfig()), dateConfig());
|
|
1324
|
+
const weekday = (dayKeyNumber(key) - dayKeyNumber(calendar.formatDateKey(calendar.startOfWeek(day, dateConfig()), dateConfig()))) % 7;
|
|
1325
|
+
const target = event.key === "ArrowLeft" ? shift(-1) : event.key === "ArrowRight" ? shift(1) : event.key === "ArrowUp" ? shift(-7) : event.key === "ArrowDown" ? shift(7) : event.key === "Home" ? shift(-weekday) : event.key === "End" ? shift(6 - weekday) : null;
|
|
1326
|
+
if (target) {
|
|
1327
|
+
event.preventDefault();
|
|
1328
|
+
moveFocus(target, event.shiftKey);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
if (event.key === "PageUp" || event.key === "PageDown") {
|
|
1332
|
+
event.preventDefault();
|
|
1333
|
+
pageTo(calendar.formatDateKey(addMonthsClamped(day, event.key === "PageUp" ? -1 : 1, dateConfig()), dateConfig()));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
if (event.key === "Enter" || (event.key === "n" || event.key === "N") && !event.shiftKey) {
|
|
1337
|
+
event.preventDefault();
|
|
1338
|
+
if (!within(selection(), key)) select(key);
|
|
1339
|
+
createOnSelection();
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
if (event.key === " ") {
|
|
1343
|
+
event.preventDefault();
|
|
1344
|
+
if (!within(selection(), key)) select(key);
|
|
1345
|
+
openDayList(key);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
if (event.key === "Escape") {
|
|
1349
|
+
if (current) closePopover(false);else if (selection()) select(null);else return;
|
|
1350
|
+
event.preventDefault();
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
// The keyboard opens the day's menu where the day is, as a right-click there would.
|
|
1354
|
+
if (hasMenu() && (event.key === "ContextMenu" || event.shiftKey && event.key === "F10")) {
|
|
1355
|
+
event.preventDefault();
|
|
1356
|
+
event.stopPropagation();
|
|
1357
|
+
menuKey = key;
|
|
1358
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
1359
|
+
event.currentTarget?.dispatchEvent(new MouseEvent("contextmenu", {
|
|
1360
|
+
bubbles: true,
|
|
1361
|
+
cancelable: true,
|
|
1362
|
+
clientX: rect.left + 12,
|
|
1363
|
+
clientY: rect.top + 12
|
|
1364
|
+
}));
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
const fromControl = event => event.target instanceof Element && Boolean(event.target.closest("a,button,[data-calendar-event],.k2b-calendar-month__lanes > *"));
|
|
1368
|
+
const onCellClick = (event, key) => {
|
|
1369
|
+
if (fromControl(event) || performance.now() < suppressClickUntil) return;
|
|
1370
|
+
// A second tap on the one selected day opens the quick create, as a double-click does with a mouse.
|
|
1371
|
+
if (lastPointer === "touch") {
|
|
1372
|
+
const range = selection();
|
|
1373
|
+
if (range && range.first === key && range.last === key && openCreate(range, {
|
|
1374
|
+
active: true
|
|
1375
|
+
})) return;
|
|
1376
|
+
select(key);
|
|
1377
|
+
closePopover(false);
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (event.shiftKey && anchorKey()) select(anchorKey(), key);else select(key);
|
|
1381
|
+
focusCell(key);
|
|
1382
|
+
const range = selection();
|
|
1383
|
+
if (range) openCreate(range, {
|
|
1384
|
+
active: false
|
|
1385
|
+
});
|
|
1386
|
+
};
|
|
1387
|
+
const onCellDoubleClick = (event, key) => {
|
|
1388
|
+
if (fromControl(event) || lastPointer === "touch") return;
|
|
1389
|
+
event.preventDefault();
|
|
1390
|
+
if (!within(selection(), key)) select(key);
|
|
1391
|
+
if (popover()?.kind === "create") activateCreate();else createOnSelection();
|
|
1392
|
+
};
|
|
1393
|
+
/** The day under the pointer: a bar that spans several days sits in the cell of its first day. */
|
|
1394
|
+
const pressedKey = (event, key) => dayKeyAtPoint(event.clientX, event.clientY) ?? key;
|
|
1395
|
+
const onCellPointerDown = (event, key) => {
|
|
1396
|
+
lastPointer = event.pointerType;
|
|
1397
|
+
// The menu acts on the pressed day, or on the selection when the press is inside it.
|
|
1398
|
+
menuKey = pressedKey(event, key);
|
|
1399
|
+
if (event.pointerType !== "mouse" || event.button !== 0 || event.shiftKey || fromControl(event)) return;
|
|
1400
|
+
cancelInteraction?.();
|
|
1401
|
+
cancelInteraction = startCalendarPointerSession({
|
|
1402
|
+
event,
|
|
1403
|
+
resolve: (clientX, clientY) => dayKeyAtPoint(clientX, clientY),
|
|
1404
|
+
onActivate: () => {
|
|
1405
|
+
suppressClickUntil = performance.now() + 400;
|
|
1406
|
+
closePopover(false);
|
|
1407
|
+
select(key);
|
|
1408
|
+
},
|
|
1409
|
+
onPreview: current => select(key, current),
|
|
1410
|
+
onCommit: current => {
|
|
1411
|
+
select(key, current);
|
|
1412
|
+
focusCell(current);
|
|
1413
|
+
const range = orderedSelection(key, current);
|
|
1414
|
+
openCreate(range, {
|
|
1415
|
+
active: range.first !== range.last
|
|
1416
|
+
});
|
|
1417
|
+
},
|
|
1418
|
+
onCancel: () => select(key)
|
|
1419
|
+
});
|
|
1420
|
+
};
|
|
1421
|
+
const onMenuOpen = () => {
|
|
1422
|
+
closePopover(false);
|
|
1423
|
+
const key = menuKey;
|
|
1424
|
+
if (key && !within(selection(), key)) select(key);
|
|
1425
|
+
};
|
|
1426
|
+
// Until a day is selected, the menu acts on the day that takes focus, so it is never empty.
|
|
1427
|
+
const menuItems = createMemo(() => {
|
|
1428
|
+
if (!hasMenu()) return [];
|
|
1429
|
+
const range = selection() ?? {
|
|
1430
|
+
first: rovingKey(),
|
|
1431
|
+
last: rovingKey()
|
|
1432
|
+
};
|
|
1433
|
+
const own = props.owner.selectionMenu?.(rangeOf(range), {
|
|
1434
|
+
quickCreate: create => {
|
|
1435
|
+
select(range.first, range.last);
|
|
1436
|
+
if (!openCreate(range, {
|
|
1437
|
+
active: true,
|
|
1438
|
+
create
|
|
1439
|
+
})) props.owner.onSlotActivate?.(rangeOf(range));
|
|
1440
|
+
}
|
|
1441
|
+
}) ?? [];
|
|
1442
|
+
const actions = own.filter(item => !("items" in item));
|
|
1443
|
+
const sections = own.filter(item => "items" in item);
|
|
1444
|
+
const navigation = [...(canOpenView("day") ? [{
|
|
1445
|
+
label: calendarMessages().calendarOpenDay,
|
|
1446
|
+
icon: "ti ti-zoom-in",
|
|
1447
|
+
action: () => openView(range.first, "day")
|
|
1448
|
+
}] : []), ...(canOpenView("week") ? [{
|
|
1449
|
+
label: calendarMessages().calendarOpenWeek,
|
|
1450
|
+
icon: "ti ti-calendar-week",
|
|
1451
|
+
action: () => openView(range.first, "week")
|
|
1452
|
+
}] : []), ...(range.first !== range.last ? [{
|
|
1453
|
+
label: calendarMessages().calendarClearSelection,
|
|
1454
|
+
icon: "ti ti-x",
|
|
1455
|
+
action: () => select(null)
|
|
1456
|
+
}] : [])];
|
|
1457
|
+
const groups = [{
|
|
1458
|
+
items: actions
|
|
1459
|
+
}, ...sections, {
|
|
1460
|
+
items: navigation
|
|
1461
|
+
}].filter(section => section.items.length > 0);
|
|
1462
|
+
// The first group names the days the menu acts on, also when the calendar's own entries are all it has.
|
|
1463
|
+
const [first, ...rest] = groups;
|
|
1464
|
+
return first && !first.sectionLabel ? [{
|
|
1465
|
+
...first,
|
|
1466
|
+
sectionLabel: selectionHeading(range)
|
|
1467
|
+
}, ...rest] : groups;
|
|
1468
|
+
});
|
|
1469
|
+
const clearMove = () => {
|
|
1470
|
+
setMovePreview(null);
|
|
1471
|
+
setMovingEventId("");
|
|
1472
|
+
};
|
|
1473
|
+
const startMove = (pointerEvent, item, markDragged) => {
|
|
1474
|
+
if (!props.owner.onEventDrop) return;
|
|
1475
|
+
const event = item.event;
|
|
1476
|
+
const grabbed = dayKeyAtPoint(pointerEvent.clientX, pointerEvent.clientY) ?? item.firstKey;
|
|
1477
|
+
const firstDay = startOfDay(event.sourceStartDate, dateConfig());
|
|
1478
|
+
cancelInteraction?.();
|
|
1479
|
+
cancelInteraction = startCalendarPointerSession({
|
|
1480
|
+
event: pointerEvent,
|
|
1481
|
+
resolve: (clientX, clientY) => {
|
|
1482
|
+
const key = dayKeyAtPoint(clientX, clientY);
|
|
1483
|
+
if (!key) return null;
|
|
1484
|
+
// The event moves by as many days as the pointer, wherever on the bar it was grabbed.
|
|
1485
|
+
const day = calendar.addDays(firstDay, dayKeyNumber(key) - dayKeyNumber(grabbed), dateConfig());
|
|
1486
|
+
return {
|
|
1487
|
+
id: event.id,
|
|
1488
|
+
title: event.title,
|
|
1489
|
+
...moveEventToDay(event, day, dateConfig())
|
|
1490
|
+
};
|
|
1491
|
+
},
|
|
1492
|
+
onActivate: () => {
|
|
1493
|
+
markDragged();
|
|
1494
|
+
suppressClickUntil = performance.now() + 400;
|
|
1495
|
+
closePopover(false);
|
|
1496
|
+
setMovingEventId(event.id);
|
|
1497
|
+
},
|
|
1498
|
+
onPreview: setMovePreview,
|
|
1499
|
+
onCommit: next => {
|
|
1500
|
+
clearMove();
|
|
1501
|
+
if (eventTimeChanged(event, next)) props.owner.onEventDrop?.(event, next);
|
|
1502
|
+
},
|
|
1503
|
+
onCancel: clearMove
|
|
1504
|
+
});
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
// Each week row measures the room below its day numbers; the rows only resize with the grid, never with content.
|
|
1508
|
+
let observer;
|
|
1509
|
+
const lanesOf = new WeakMap();
|
|
1510
|
+
const measure = (element, week) => {
|
|
1511
|
+
const style = getComputedStyle(element);
|
|
1512
|
+
const rootSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
|
1513
|
+
const capacity = monthLaneCapacity(element.clientHeight, MONTH_LANE_REM * rootSize, MONTH_LANE_GAP_REM * rootSize);
|
|
1514
|
+
setCapacities(current => {
|
|
1515
|
+
if (current[week] === capacity) return current;
|
|
1516
|
+
const next = [...current];
|
|
1517
|
+
next[week] = capacity;
|
|
1518
|
+
return next;
|
|
1519
|
+
});
|
|
1520
|
+
if (week === 0) {
|
|
1521
|
+
setCellWidth(element.clientWidth);
|
|
1522
|
+
setBarFont(`600 ${0.6875 * rootSize}px ${style.fontFamily}`);
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
const observeLanes = (element, week) => {
|
|
1526
|
+
lanesOf.set(element, week);
|
|
1527
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
1528
|
+
observer ??= new ResizeObserver(entries => {
|
|
1529
|
+
for (const entry of entries) {
|
|
1530
|
+
const index = lanesOf.get(entry.target);
|
|
1531
|
+
if (index !== undefined) measure(entry.target, index);
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1534
|
+
observer.observe(element);
|
|
1535
|
+
onCleanup(() => observer?.unobserve(element));
|
|
1536
|
+
};
|
|
1537
|
+
/** A date hint shows only when the bar holds its title and the hint whole; screen readers get the range anyway. */
|
|
1538
|
+
const hintFits = (title, hint, span) => {
|
|
1539
|
+
const width = cellWidth();
|
|
1540
|
+
const font = barFont();
|
|
1541
|
+
if (width <= 0 || !font) return false;
|
|
1542
|
+
const rootSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
|
1543
|
+
return textWidth(title, font) + textWidth(hint, font) + MONTH_HINT_SLACK_REM * rootSize <= span * width;
|
|
1544
|
+
};
|
|
1545
|
+
createEffect(() => {
|
|
1546
|
+
const current = popover();
|
|
1547
|
+
const element = popoverElement;
|
|
1548
|
+
if (!element) return;
|
|
1549
|
+
if (!current) {
|
|
1550
|
+
if (element.matches(":popover-open")) element.hidePopover();
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
if (!element.matches(":popover-open")) element.showPopover();
|
|
1554
|
+
const focus = untrack(createActive);
|
|
1555
|
+
queueMicrotask(() => {
|
|
1556
|
+
if (popover() !== current || !element.isConnected) return;
|
|
1557
|
+
placeCalendarPopover(current.anchor, element);
|
|
1558
|
+
if (focus) popoverFocusable()?.focus();
|
|
1559
|
+
});
|
|
1560
|
+
});
|
|
1561
|
+
onMount(() => {
|
|
1562
|
+
const reposition = () => {
|
|
1563
|
+
const current = popover();
|
|
1564
|
+
if (current && popoverElement) placeCalendarPopover(current.anchor, popoverElement);
|
|
1565
|
+
};
|
|
1566
|
+
window.addEventListener("resize", reposition);
|
|
1567
|
+
window.addEventListener("scroll", reposition, true);
|
|
1568
|
+
onCleanup(() => {
|
|
1569
|
+
window.removeEventListener("resize", reposition);
|
|
1570
|
+
window.removeEventListener("scroll", reposition, true);
|
|
1571
|
+
});
|
|
1572
|
+
});
|
|
1573
|
+
onCleanup(() => {
|
|
1574
|
+
cancelInteraction?.();
|
|
1575
|
+
observer?.disconnect();
|
|
1576
|
+
});
|
|
1577
|
+
const dayEntries = dayKey => items().filter(item => item.days.has(dayKey));
|
|
1578
|
+
/** Every bar of a long entry lights up with the one under the pointer. */
|
|
1579
|
+
const linkProps = item => ({
|
|
1580
|
+
onPointerEnter: event => {
|
|
1581
|
+
if (event.pointerType === "mouse") setLinkedItem(item.key);
|
|
1582
|
+
},
|
|
1583
|
+
onPointerLeave: () => {
|
|
1584
|
+
if (linkedItem() === item.key) setLinkedItem("");
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
const grid = () => _$ssr(_tmpl$27, _$ssrAttribute("aria-label", _$escape(formatMonth(props.date, dateConfig()), true), false), _$ssrStyleProperty("grid-template-rows:", `auto repeat(${_$escape(weeks().length, true)}, minmax(5rem, 1fr))`) + _$ssrStyleProperty(";--k2b-calendar-lane:", "1.25rem") + _$ssrStyleProperty(";--k2b-calendar-lane-gap:", "0.125rem"), _$ssrAttribute("data-week-numbers", props.owner.withWeekNumbers ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
1588
|
+
get when() {
|
|
1589
|
+
return props.owner.withWeekNumbers;
|
|
1590
|
+
},
|
|
1591
|
+
get children() {
|
|
1592
|
+
return _$ssr(_tmpl$26, _$escape(messages().weekShort));
|
|
1593
|
+
}
|
|
1594
|
+
})), _$escape(_$createComponent(For, {
|
|
1595
|
+
get each() {
|
|
1596
|
+
return weekdays();
|
|
1597
|
+
},
|
|
1598
|
+
children: (day, index) => _$ssr(_tmpl$28, _$ssrAttribute("data-weekend", index() > 4 ? "true" : _$escape(undefined, true), false), _$escape(day))
|
|
1599
|
+
})), _$escape(_$createComponent(For, {
|
|
1600
|
+
get each() {
|
|
1601
|
+
return weeks();
|
|
1602
|
+
},
|
|
1603
|
+
children: (week, weekIndex) => {
|
|
1604
|
+
const fit = createMemo(() => fitMonthWeek(weekLayouts()[weekIndex()] ?? {
|
|
1605
|
+
bars: [],
|
|
1606
|
+
singles: []
|
|
1607
|
+
}, capacities()[weekIndex()] ?? MONTH_DEFAULT_LANES));
|
|
1608
|
+
const weekHref = () => viewHref(week[0].key, "week");
|
|
1609
|
+
const weekNumber = () => zonedWeekNumber(week[0].day, dateConfig());
|
|
1610
|
+
return _$ssr(_tmpl$30, _$ssrAttribute("data-week-numbers", props.owner.withWeekNumbers ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
1611
|
+
get when() {
|
|
1612
|
+
return props.owner.withWeekNumbers;
|
|
1613
|
+
},
|
|
1614
|
+
get children() {
|
|
1615
|
+
return _$ssr(_tmpl$29, _$escape(_$createComponent(Show, {
|
|
1616
|
+
get when() {
|
|
1617
|
+
return weekHref();
|
|
1618
|
+
},
|
|
1619
|
+
get fallback() {
|
|
1620
|
+
return _$createComponent(Show, {
|
|
1621
|
+
get when() {
|
|
1622
|
+
return canOpenView("week");
|
|
1623
|
+
},
|
|
1624
|
+
get fallback() {
|
|
1625
|
+
return weekNumber();
|
|
1626
|
+
},
|
|
1627
|
+
get children() {
|
|
1628
|
+
return _$ssr(_tmpl$31, _$ssrAttribute("title", _$escape(calendarMessages().calendarOpenWeekNumber({
|
|
1629
|
+
week: weekNumber()
|
|
1630
|
+
}), true), false) + _$ssrAttribute("aria-label", _$escape(calendarMessages().calendarOpenWeekNumber({
|
|
1631
|
+
week: weekNumber()
|
|
1632
|
+
}), true), false), _$escape(weekNumber()));
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
},
|
|
1636
|
+
children: href => _$createComponent(CalendarNavigationLink, {
|
|
1637
|
+
get owner() {
|
|
1638
|
+
return props.owner;
|
|
1639
|
+
},
|
|
1640
|
+
get href() {
|
|
1641
|
+
return href();
|
|
1642
|
+
},
|
|
1643
|
+
get anchorProps() {
|
|
1644
|
+
return {
|
|
1645
|
+
class: "k2b-calendar-month__week-link",
|
|
1646
|
+
tabIndex: -1,
|
|
1647
|
+
title: calendarMessages().calendarOpenWeekNumber({
|
|
1648
|
+
week: weekNumber()
|
|
1649
|
+
}),
|
|
1650
|
+
"aria-label": calendarMessages().calendarOpenWeekNumber({
|
|
1651
|
+
week: weekNumber()
|
|
1652
|
+
})
|
|
1653
|
+
};
|
|
1654
|
+
},
|
|
1655
|
+
get children() {
|
|
1656
|
+
return weekNumber();
|
|
1657
|
+
}
|
|
1658
|
+
})
|
|
1659
|
+
})));
|
|
1660
|
+
}
|
|
1661
|
+
})), _$escape(_$createComponent(For, {
|
|
1662
|
+
each: week,
|
|
1663
|
+
children: ({
|
|
1664
|
+
day,
|
|
1665
|
+
key
|
|
1666
|
+
}, column) => {
|
|
1667
|
+
const dayBadge = props.owner.dayBadges?.[key];
|
|
1668
|
+
const sameMonth = calendar.isSameMonth(day, props.date, dateConfig());
|
|
1669
|
+
const label = dayLabel(day);
|
|
1670
|
+
const bars = createMemo(() => fit().bars.filter(bar => bar.startColumn === column()));
|
|
1671
|
+
const cell = () => fit().cells[column()] ?? {
|
|
1672
|
+
top: 0,
|
|
1673
|
+
shown: [],
|
|
1674
|
+
hidden: 0
|
|
1675
|
+
};
|
|
1676
|
+
return _$ssr(_tmpl$33, _$ssrAttribute("tabindex", rovingKey() === key ? 0 : -1, false) + _$ssrAttribute("aria-label", _$escape(label, true), false) + _$ssrAttribute("aria-selected", within(selection(), key) ? "true" : "false", false) + _$ssrAttribute("aria-current", key === todayKey() ? "date" : _$escape(undefined, true), false) + _$ssrAttribute("data-calendar-day-key", _$escape(key, true), false) + _$ssrAttribute("data-outside", !sameMonth ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-weekend", column() > 4 ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-past", key < todayKey() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-selected", _$escape(selectionPart(key), true), false) + _$ssrAttribute("data-drop-preview", within(dropRange(), key) ? "true" : _$escape(undefined, true), false), _$ssrAttribute("data-today", key === todayKey() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-outside", !sameMonth ? "true" : _$escape(undefined, true), false), _$escape(calendar.formatDayNumber(day, dateConfig())), _$escape(_$createComponent(Show, {
|
|
1677
|
+
when: dayBadge,
|
|
1678
|
+
children: badge => _$ssr(_tmpl$24, _$escape(_$createComponent(Show, {
|
|
1679
|
+
get when() {
|
|
1680
|
+
return badge().icon;
|
|
1681
|
+
},
|
|
1682
|
+
children: icon => _$ssr(_tmpl$10, `k2b-calendar-month__badge-icon ti ti-${_$escape(icon(), true)}`)
|
|
1683
|
+
})), _$escape(badge().label))
|
|
1684
|
+
})), _$escape(_$createComponent(For, {
|
|
1685
|
+
get each() {
|
|
1686
|
+
return bars();
|
|
1687
|
+
},
|
|
1688
|
+
children: bar => {
|
|
1689
|
+
const span = bar.endColumn - bar.startColumn + 1;
|
|
1690
|
+
const startHint = () => {
|
|
1691
|
+
if (!bar.continuesBefore) return undefined;
|
|
1692
|
+
const hint = calendarMessages().calendarContinuesFrom(hintDate(bar.item.firstKey));
|
|
1693
|
+
return hintFits(bar.item.event.title, hint, span) ? hint : undefined;
|
|
1694
|
+
};
|
|
1695
|
+
const endHint = () => {
|
|
1696
|
+
if (!bar.continuesAfter) return undefined;
|
|
1697
|
+
const hint = calendarMessages().calendarContinuesUntil(hintDate(bar.item.lastKey));
|
|
1698
|
+
return hintFits(`${bar.item.event.title}${startHint() ?? ""}`, hint, span) ? hint : undefined;
|
|
1699
|
+
};
|
|
1700
|
+
return _$ssrElement("div", _$mergeProps({
|
|
1701
|
+
"class": "k2b-calendar-month__segment",
|
|
1702
|
+
get style() {
|
|
1703
|
+
return {
|
|
1704
|
+
"--k2b-calendar-span": String(span),
|
|
1705
|
+
"--k2b-calendar-row": String(bar.lane)
|
|
1706
|
+
};
|
|
1707
|
+
},
|
|
1708
|
+
"data-bar": "true",
|
|
1709
|
+
get ["data-linked"]() {
|
|
1710
|
+
return linkedItem() === bar.item.key && bar.item.firstKey !== bar.item.lastKey ? "true" : undefined;
|
|
1711
|
+
},
|
|
1712
|
+
get ["data-continues-before"]() {
|
|
1713
|
+
return bar.continuesBefore ? "true" : undefined;
|
|
1714
|
+
},
|
|
1715
|
+
get ["data-continues-after"]() {
|
|
1716
|
+
return bar.continuesAfter ? "true" : undefined;
|
|
1717
|
+
}
|
|
1718
|
+
}, () => linkProps(bar.item)), _$escape(_$createComponent(EventChip, {
|
|
1719
|
+
get event() {
|
|
1720
|
+
return bar.item.event;
|
|
1721
|
+
},
|
|
1722
|
+
get owner() {
|
|
1723
|
+
return props.owner;
|
|
1724
|
+
},
|
|
1725
|
+
get href() {
|
|
1726
|
+
return eventHref(props.owner, bar.item.event);
|
|
1727
|
+
},
|
|
1728
|
+
get label() {
|
|
1729
|
+
return itemLabel(bar.item);
|
|
1730
|
+
},
|
|
1731
|
+
compact: true,
|
|
1732
|
+
bar: true,
|
|
1733
|
+
get continuesBefore() {
|
|
1734
|
+
return bar.continuesBefore;
|
|
1735
|
+
},
|
|
1736
|
+
get continuesAfter() {
|
|
1737
|
+
return bar.continuesAfter;
|
|
1738
|
+
},
|
|
1739
|
+
get hintStart() {
|
|
1740
|
+
return startHint();
|
|
1741
|
+
},
|
|
1742
|
+
get hintEnd() {
|
|
1743
|
+
return endHint();
|
|
1744
|
+
},
|
|
1745
|
+
get moving() {
|
|
1746
|
+
return movingEventId() === bar.item.event.id;
|
|
1747
|
+
},
|
|
1748
|
+
get onMovePointerDown() {
|
|
1749
|
+
return props.owner.onEventDrop ? (pointerEvent, markDragged) => startMove(pointerEvent, bar.item, markDragged) : undefined;
|
|
1750
|
+
}
|
|
1751
|
+
})), false);
|
|
1752
|
+
}
|
|
1753
|
+
})), _$escape(_$createComponent(For, {
|
|
1754
|
+
get each() {
|
|
1755
|
+
return cell().shown;
|
|
1756
|
+
},
|
|
1757
|
+
children: (item, index) => _$ssr(_tmpl$34, _$ssrStyleProperty("--k2b-calendar-span:", "1") + _$ssrStyleProperty(";--k2b-calendar-row:", _$escape(String(cell().top + index()), true)), _$escape(_$createComponent(EventChip, {
|
|
1758
|
+
get event() {
|
|
1759
|
+
return item.event;
|
|
1760
|
+
},
|
|
1761
|
+
get owner() {
|
|
1762
|
+
return props.owner;
|
|
1763
|
+
},
|
|
1764
|
+
get href() {
|
|
1765
|
+
return eventHref(props.owner, item.event);
|
|
1766
|
+
},
|
|
1767
|
+
compact: true,
|
|
1768
|
+
get leadingTime() {
|
|
1769
|
+
return leadingTime(item);
|
|
1770
|
+
},
|
|
1771
|
+
get moving() {
|
|
1772
|
+
return movingEventId() === item.event.id;
|
|
1773
|
+
},
|
|
1774
|
+
get onMovePointerDown() {
|
|
1775
|
+
return props.owner.onEventDrop ? (pointerEvent, markDragged) => startMove(pointerEvent, item, markDragged) : undefined;
|
|
1776
|
+
}
|
|
1777
|
+
})))
|
|
1778
|
+
})), _$escape(_$createComponent(Show, {
|
|
1779
|
+
get when() {
|
|
1780
|
+
return cell().hidden > 0;
|
|
1781
|
+
},
|
|
1782
|
+
get children() {
|
|
1783
|
+
return _$ssr(_tmpl$32, _$ssrStyleProperty("--k2b-calendar-row:", _$escape(String(cell().top + cell().shown.length), true)), _$ssrAttribute("aria-label", _$escape(calendarMessages().calendarMoreEventsOn({
|
|
1784
|
+
count: cell().hidden,
|
|
1785
|
+
label
|
|
1786
|
+
}), true), false), _$ssrAttribute("aria-expanded", (() => {
|
|
1787
|
+
const current = popover();
|
|
1788
|
+
return current?.kind === "day" && _$escape(current.dayKey, true) === _$escape(key, true);
|
|
1789
|
+
})(), false), _$escape(calendarMessages().calendarMoreEvents({
|
|
1790
|
+
count: cell().hidden
|
|
1791
|
+
})), _$escape(cell().hidden));
|
|
1792
|
+
}
|
|
1793
|
+
})));
|
|
1794
|
+
}
|
|
1795
|
+
})));
|
|
1796
|
+
}
|
|
1797
|
+
})));
|
|
1798
|
+
const dayList = dayKey => {
|
|
1799
|
+
const day = parseKey(dayKey);
|
|
1800
|
+
const href = viewHref(dayKey, "day");
|
|
1801
|
+
// Read where it renders, so a refresh while the list is open updates it as it does the grid.
|
|
1802
|
+
const entries = () => dayEntries(dayKey);
|
|
1803
|
+
return _$ssr(_tmpl$37, _$escape(dayLabel(day)), _$escape(_$createComponent(Show, {
|
|
1804
|
+
get when() {
|
|
1805
|
+
return entries().length > 0;
|
|
1806
|
+
},
|
|
1807
|
+
get fallback() {
|
|
1808
|
+
return _$ssr(_tmpl$38, _$escape(calendarMessages().calendarNothingPlanned));
|
|
1809
|
+
},
|
|
1810
|
+
get children() {
|
|
1811
|
+
return _$ssr(_tmpl$35, _$escape(_$createComponent(For, {
|
|
1812
|
+
get each() {
|
|
1813
|
+
return entries();
|
|
1814
|
+
},
|
|
1815
|
+
children: item => _$ssr(_tmpl$39, _$escape(_$createComponent(EventChip, {
|
|
1816
|
+
get event() {
|
|
1817
|
+
return item.event;
|
|
1818
|
+
},
|
|
1819
|
+
get owner() {
|
|
1820
|
+
return props.owner;
|
|
1821
|
+
},
|
|
1822
|
+
get href() {
|
|
1823
|
+
return eventHref(props.owner, item.event);
|
|
1824
|
+
},
|
|
1825
|
+
get label() {
|
|
1826
|
+
return itemLabel(item);
|
|
1827
|
+
},
|
|
1828
|
+
compact: true,
|
|
1829
|
+
get bar() {
|
|
1830
|
+
return item.bar;
|
|
1831
|
+
},
|
|
1832
|
+
get leadingTime() {
|
|
1833
|
+
return leadingTime(item);
|
|
1834
|
+
}
|
|
1835
|
+
})))
|
|
1836
|
+
})));
|
|
1837
|
+
}
|
|
1838
|
+
})), _$escape(_$createComponent(Show, {
|
|
1839
|
+
when: href,
|
|
1840
|
+
get fallback() {
|
|
1841
|
+
return _$createComponent(Show, {
|
|
1842
|
+
get when() {
|
|
1843
|
+
return canOpenView("day");
|
|
1844
|
+
},
|
|
1845
|
+
get children() {
|
|
1846
|
+
return _$createComponent(Button, {
|
|
1847
|
+
type: "button",
|
|
1848
|
+
variant: "ghost",
|
|
1849
|
+
size: "sm",
|
|
1850
|
+
onClick: () => openView(dayKey, "day"),
|
|
1851
|
+
get children() {
|
|
1852
|
+
return [_$ssr(_tmpl$40), calendarMessages().calendarOpenDay];
|
|
1853
|
+
}
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
});
|
|
1857
|
+
},
|
|
1858
|
+
children: dayHref => _$createComponent(CalendarNavigationLink, {
|
|
1859
|
+
get owner() {
|
|
1860
|
+
return props.owner;
|
|
1861
|
+
},
|
|
1862
|
+
get href() {
|
|
1863
|
+
return dayHref();
|
|
1864
|
+
},
|
|
1865
|
+
anchorProps: {
|
|
1866
|
+
class: "k2b-button",
|
|
1867
|
+
"data-variant": "ghost",
|
|
1868
|
+
"data-size": "sm"
|
|
1869
|
+
},
|
|
1870
|
+
get children() {
|
|
1871
|
+
return [_$ssr(_tmpl$40), calendarMessages().calendarOpenDay];
|
|
1872
|
+
}
|
|
1873
|
+
})
|
|
1874
|
+
})), _$escape(_$createComponent(Show, {
|
|
1875
|
+
get when() {
|
|
1876
|
+
return props.owner.renderQuickCreate;
|
|
1877
|
+
},
|
|
1878
|
+
get children() {
|
|
1879
|
+
return _$createComponent(Button, {
|
|
1880
|
+
type: "button",
|
|
1881
|
+
variant: "ghost",
|
|
1882
|
+
size: "sm",
|
|
1883
|
+
onClick: () => {
|
|
1884
|
+
select(dayKey);
|
|
1885
|
+
openCreate({
|
|
1886
|
+
first: dayKey,
|
|
1887
|
+
last: dayKey
|
|
1888
|
+
}, {
|
|
1889
|
+
active: true
|
|
1890
|
+
});
|
|
1891
|
+
},
|
|
1892
|
+
get children() {
|
|
1893
|
+
return [_$ssr(_tmpl$36), calendarMessages().calendarNewEvent];
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
})));
|
|
1898
|
+
};
|
|
1899
|
+
return [_$createComponent(Show, {
|
|
1900
|
+
get when() {
|
|
1901
|
+
return hasMenu();
|
|
1902
|
+
},
|
|
1903
|
+
get fallback() {
|
|
1904
|
+
return grid();
|
|
1905
|
+
},
|
|
1906
|
+
get children() {
|
|
1907
|
+
return _$createComponent(GestureMenu, {
|
|
1908
|
+
get items() {
|
|
1909
|
+
return menuItems();
|
|
1910
|
+
},
|
|
1911
|
+
get label() {
|
|
1912
|
+
return calendarMessages().calendarSelectionActions;
|
|
1913
|
+
},
|
|
1914
|
+
tabIndex: -1,
|
|
1915
|
+
onOpen: onMenuOpen,
|
|
1916
|
+
"class": "k2b-calendar-month-menu",
|
|
1917
|
+
get children() {
|
|
1918
|
+
return grid();
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
}), _$ssr(_tmpl$41, _$ssrAttribute("aria-label", (() => {
|
|
1923
|
+
const current = popover();
|
|
1924
|
+
if (current?.kind === "day") return dayLabel(parseKey(current.dayKey));
|
|
1925
|
+
return _$escape(calendarMessages().calendarQuickCreate, true);
|
|
1926
|
+
})(), false) + _$ssrAttribute("data-kind", _$escape(popover()?.kind, true), false) + _$ssrAttribute("data-quiet", popover()?.kind === "create" && !createActive() ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
1927
|
+
get when() {
|
|
1928
|
+
return popover();
|
|
1929
|
+
},
|
|
1930
|
+
keyed: true,
|
|
1931
|
+
children: value => value.kind === "create" ? props.owner.renderQuickCreate?.(rangeOf(value.range), {
|
|
1932
|
+
create: value.create,
|
|
1933
|
+
close: () => {
|
|
1934
|
+
if (popover() === value) closePopover();
|
|
1935
|
+
}
|
|
1936
|
+
}) : dayList(value.dayKey)
|
|
1937
|
+
})))];
|
|
1938
|
+
};
|
|
827
1939
|
const TimeGridView = props => {
|
|
828
1940
|
const messages = useUiMessages();
|
|
829
1941
|
const dateConfig = createMemo(() => ownerDateConfig(props.owner));
|
|
@@ -1035,7 +2147,7 @@ const TimeGridView = props => {
|
|
|
1035
2147
|
});
|
|
1036
2148
|
});
|
|
1037
2149
|
});
|
|
1038
|
-
return _$ssr(_tmpl$
|
|
2150
|
+
return _$ssr(_tmpl$43, _$ssrStyleProperty("grid-template-columns:", `4rem repeat(${_$escape(props.days.length, true)}, minmax(0, 1fr))`), _$escape(_$createComponent(For, {
|
|
1039
2151
|
get each() {
|
|
1040
2152
|
return props.days;
|
|
1041
2153
|
},
|
|
@@ -1053,13 +2165,13 @@ const TimeGridView = props => {
|
|
|
1053
2165
|
class: "k2b-calendar-time-grid__day"
|
|
1054
2166
|
},
|
|
1055
2167
|
get children() {
|
|
1056
|
-
return [_$ssr(_tmpl$
|
|
2168
|
+
return [_$ssr(_tmpl$44, _$ssrAttribute("data-today", today() ? "true" : _$escape(undefined, true), false), _$escape(formatDay(day, dateConfig()))), _$createComponent(Show, {
|
|
1057
2169
|
when: dayBadge,
|
|
1058
|
-
children: badge => _$ssr(_tmpl$
|
|
2170
|
+
children: badge => _$ssr(_tmpl$45, _$escape(_$createComponent(Show, {
|
|
1059
2171
|
get when() {
|
|
1060
2172
|
return badge().icon;
|
|
1061
2173
|
},
|
|
1062
|
-
children: icon => _$ssr(_tmpl$
|
|
2174
|
+
children: icon => _$ssr(_tmpl$10, `k2b-calendar-time-grid__badge-icon ti ti-${_$escape(icon(), true)}`)
|
|
1063
2175
|
})), _$escape(badge().label))
|
|
1064
2176
|
})];
|
|
1065
2177
|
}
|
|
@@ -1070,7 +2182,7 @@ const TimeGridView = props => {
|
|
|
1070
2182
|
return !props.owner.hideAllDay;
|
|
1071
2183
|
},
|
|
1072
2184
|
get children() {
|
|
1073
|
-
return _$ssr(_tmpl$
|
|
2185
|
+
return _$ssr(_tmpl$42, _$ssrStyleProperty("grid-template-columns:", `4rem repeat(${_$escape(props.days.length, true)}, minmax(0, 1fr))`) + _$ssrStyleProperty(";max-height:", `${_$escape(props.owner.allDayMaxHeightRem ?? 7, true)}rem`), _$escape(props.labels.allDay), _$escape(_$createComponent(For, {
|
|
1074
2186
|
get each() {
|
|
1075
2187
|
return props.days;
|
|
1076
2188
|
},
|
|
@@ -1094,9 +2206,9 @@ const TimeGridView = props => {
|
|
|
1094
2206
|
end: calendar.addDays(start, 1, dateConfig()),
|
|
1095
2207
|
allDay: true
|
|
1096
2208
|
};
|
|
1097
|
-
}, () => performance.now() < suppressSlotClickUntil)), _$ssr(_tmpl$
|
|
2209
|
+
}, () => performance.now() < suppressSlotClickUntil)), _$ssr(_tmpl$46, _$escape(_$createComponent(For, {
|
|
1098
2210
|
each: previewAllDay,
|
|
1099
|
-
children: event => _$ssr(_tmpl$
|
|
2211
|
+
children: event => _$ssr(_tmpl$21, _$escape(event.title))
|
|
1100
2212
|
})), _$escape(_$createComponent(For, {
|
|
1101
2213
|
get each() {
|
|
1102
2214
|
return allDay();
|
|
@@ -1125,7 +2237,7 @@ const TimeGridView = props => {
|
|
|
1125
2237
|
get each() {
|
|
1126
2238
|
return hours();
|
|
1127
2239
|
},
|
|
1128
|
-
children: hour => _$ssr(_tmpl$
|
|
2240
|
+
children: hour => _$ssr(_tmpl$47, _$ssrAttribute("data-outside-business", hour < businessStartHour() || hour > businessEndHour() ? "true" : _$escape(undefined, true), false), _$escape(`${hour}`.padStart(2, "0")))
|
|
1129
2241
|
})), _$escape(_$createComponent(For, {
|
|
1130
2242
|
get each() {
|
|
1131
2243
|
return props.days;
|
|
@@ -1133,12 +2245,12 @@ const TimeGridView = props => {
|
|
|
1133
2245
|
children: day => {
|
|
1134
2246
|
const dayKey = calendar.formatDateKey(day, dateConfig());
|
|
1135
2247
|
const layouts = createMemo(() => timedEventLayouts(eventsByDay().get(dayKey)?.timed ?? []));
|
|
1136
|
-
return _$ssr(_tmpl$
|
|
2248
|
+
return _$ssr(_tmpl$49, _$ssrStyleProperty("min-width:", _$escape(dayColumnMinWidth(layouts()), true)) + _$ssrStyleProperty(";grid-template-rows:", `repeat(${_$escape(hours().length, true)}, minmax(4rem, 1fr))`), _$escape(_$createComponent(Show, {
|
|
1137
2249
|
get when() {
|
|
1138
2250
|
return currentTimeLine(day) !== null;
|
|
1139
2251
|
},
|
|
1140
2252
|
get children() {
|
|
1141
|
-
return _$ssr(_tmpl$
|
|
2253
|
+
return _$ssr(_tmpl$48, _$ssrStyleProperty("top:", `${_$escape(currentTimeLine(day) ?? 0, true)}%`));
|
|
1142
2254
|
}
|
|
1143
2255
|
})), _$escape(_$createComponent(For, {
|
|
1144
2256
|
get each() {
|
|
@@ -1167,12 +2279,12 @@ const TimeGridView = props => {
|
|
|
1167
2279
|
children: event => {
|
|
1168
2280
|
const layout = eventLayout(event);
|
|
1169
2281
|
const short = event.endDate.getTime() - event.startDate.getTime() < 60 * 60_000;
|
|
1170
|
-
return _$ssr(_tmpl$
|
|
2282
|
+
return _$ssr(_tmpl$51, _$ssrAttribute("data-short", short ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("top:", `${_$escape(layout.top, true)}%`) + _$ssrStyleProperty(";height:", `${_$escape(layout.height, true)}%`), _$escape(_$createComponent(Show, {
|
|
1171
2283
|
get when() {
|
|
1172
2284
|
return event.title;
|
|
1173
2285
|
},
|
|
1174
2286
|
get children() {
|
|
1175
|
-
return _$ssr(_tmpl$
|
|
2287
|
+
return _$ssr(_tmpl$50, _$escape(event.title));
|
|
1176
2288
|
}
|
|
1177
2289
|
})), _$escape(formatTime(event.startDate, dateConfig())), _$escape(formatTime(event.endDate, dateConfig())));
|
|
1178
2290
|
}
|
|
@@ -1218,7 +2330,7 @@ const TimeGridView = props => {
|
|
|
1218
2330
|
onCancel: clearInteraction
|
|
1219
2331
|
});
|
|
1220
2332
|
};
|
|
1221
|
-
return _$ssr(_tmpl$
|
|
2333
|
+
return _$ssr(_tmpl$53, _$ssrStyleProperty("top:", `${_$escape(layout.top, true)}%`) + _$ssrStyleProperty(";height:", `${_$escape(layout.height, true)}%`) + _$ssrStyleProperty(";left:", _$escape(position.left, true)) + _$ssrStyleProperty(";width:", _$escape(position.width, true)) + _$ssrStyleProperty(";z-index:", _$escape(String(20 + layoutItem.lane), true)), _$escape(_$createComponent(EventChip, {
|
|
1222
2334
|
event: event,
|
|
1223
2335
|
get owner() {
|
|
1224
2336
|
return props.owner;
|
|
@@ -1238,7 +2350,7 @@ const TimeGridView = props => {
|
|
|
1238
2350
|
return props.owner.onEventResize;
|
|
1239
2351
|
},
|
|
1240
2352
|
get children() {
|
|
1241
|
-
return _$ssr(_tmpl$
|
|
2353
|
+
return _$ssr(_tmpl$52, _$ssrAttribute("aria-label", _$escape(messages().resizeEvent, true), false));
|
|
1242
2354
|
}
|
|
1243
2355
|
})));
|
|
1244
2356
|
}
|
|
@@ -1250,14 +2362,14 @@ const TimeGridView = props => {
|
|
|
1250
2362
|
const layout = timeRangeLayout(overflow.groupStartDate, overflow.groupEndDate);
|
|
1251
2363
|
const key = `${dayKey}-${overflow.groupId}`;
|
|
1252
2364
|
const hiddenTitle = () => overflow.hiddenEvents.map(event => `${formatTime(event.startDate, dateConfig())} ${event.title}`).join("\n");
|
|
1253
|
-
return [_$ssr(_tmpl$
|
|
2365
|
+
return [_$ssr(_tmpl$54, _$ssrStyleProperty("top:", `${_$escape(layout.top, true)}%`) + _$ssrStyleProperty(";height:", `${_$escape(layout.height, true)}%`), _$ssrAttribute("title", _$escape(hiddenTitle(), true), false) + _$ssrAttribute("aria-label", _$escape(messages().hiddenOverlappingEvents({
|
|
1254
2366
|
count: overflow.hiddenEvents.length
|
|
1255
2367
|
}), true), false), _$escape(overflow.hiddenEvents.length)), _$createComponent(Show, {
|
|
1256
2368
|
get when() {
|
|
1257
2369
|
return expandedOverflow() === key;
|
|
1258
2370
|
},
|
|
1259
2371
|
get children() {
|
|
1260
|
-
return _$ssr(_tmpl$
|
|
2372
|
+
return _$ssr(_tmpl$55, _$ssrStyleProperty("top:", `${_$escape(layout.top, true)}%`), _$escape(formatTime(overflow.groupStartDate, dateConfig())), _$escape(formatTime(overflow.groupEndDate, dateConfig())), _$escape(_$createComponent(For, {
|
|
1261
2373
|
get each() {
|
|
1262
2374
|
return overflow.hiddenEvents;
|
|
1263
2375
|
},
|
|
@@ -1316,11 +2428,11 @@ const YearView = props => {
|
|
|
1316
2428
|
};
|
|
1317
2429
|
});
|
|
1318
2430
|
});
|
|
1319
|
-
return _$ssr(_tmpl$
|
|
2431
|
+
return _$ssr(_tmpl$56, _$escape(_$createComponent(For, {
|
|
1320
2432
|
get each() {
|
|
1321
2433
|
return months();
|
|
1322
2434
|
},
|
|
1323
|
-
children: month => _$ssr(_tmpl$
|
|
2435
|
+
children: month => _$ssr(_tmpl$57, _$escape(month.label), _$escape(_$createComponent(For, {
|
|
1324
2436
|
get each() {
|
|
1325
2437
|
return month.days;
|
|
1326
2438
|
},
|
|
@@ -1343,7 +2455,7 @@ const YearView = props => {
|
|
|
1343
2455
|
get when() {
|
|
1344
2456
|
return day.events[0];
|
|
1345
2457
|
},
|
|
1346
|
-
children: event => _$ssr(_tmpl$
|
|
2458
|
+
children: event => _$ssr(_tmpl$58, _$ssrAttribute("data-color", event().colorHex ? _$escape(undefined, true) : _$escape(event().color ?? "blue", true), false) + _$ssrAttribute("data-today", day.isToday ? "true" : _$escape(undefined, true), false), _$ssrStyle(event().colorHex ? {
|
|
1347
2459
|
"background-color": day.isToday ? "white" : event().colorHex
|
|
1348
2460
|
} : undefined))
|
|
1349
2461
|
})];
|
|
@@ -1356,7 +2468,7 @@ const MobileMonthView = props => {
|
|
|
1356
2468
|
const dateConfig = createMemo(() => ownerDateConfig(props.owner));
|
|
1357
2469
|
const selectedDateKey = createMemo(() => calendar.formatDateKey(props.selectedDate, dateConfig()));
|
|
1358
2470
|
const selectedEvents = createMemo(() => props.events.filter(event => event.dayKey === selectedDateKey()));
|
|
1359
|
-
return _$ssr(_tmpl$
|
|
2471
|
+
return _$ssr(_tmpl$60, _$escape(_$createComponent(MonthPickerView, {
|
|
1360
2472
|
get owner() {
|
|
1361
2473
|
return {
|
|
1362
2474
|
...props.owner,
|
|
@@ -1386,10 +2498,10 @@ const MobileMonthView = props => {
|
|
|
1386
2498
|
return selectedEvents().length > 0;
|
|
1387
2499
|
},
|
|
1388
2500
|
get fallback() {
|
|
1389
|
-
return _$ssr(_tmpl$
|
|
2501
|
+
return _$ssr(_tmpl$61, _$escape(props.labels.noEvents));
|
|
1390
2502
|
},
|
|
1391
2503
|
get children() {
|
|
1392
|
-
return _$ssr(_tmpl$
|
|
2504
|
+
return _$ssr(_tmpl$59, _$escape(_$createComponent(For, {
|
|
1393
2505
|
get each() {
|
|
1394
2506
|
return selectedEvents();
|
|
1395
2507
|
},
|
|
@@ -1406,7 +2518,7 @@ const MobileMonthView = props => {
|
|
|
1406
2518
|
}
|
|
1407
2519
|
})));
|
|
1408
2520
|
};
|
|
1409
|
-
const CalendarBody = props => _$ssr(_tmpl$
|
|
2521
|
+
const CalendarBody = props => _$ssr(_tmpl$62, _$escape(props.children));
|
|
1410
2522
|
const CalendarRoot = props => {
|
|
1411
2523
|
const messages = useUiMessages();
|
|
1412
2524
|
// Subviews derive their date config from the owner props, so the inherited
|
|
@@ -1427,6 +2539,8 @@ const CalendarRoot = props => {
|
|
|
1427
2539
|
const date = createMemo(() => safeDate(props.date));
|
|
1428
2540
|
const selectedDate = createMemo(() => safeDate(props.selectedDate ?? props.date));
|
|
1429
2541
|
const [now, setNow] = createSignal(new Date());
|
|
2542
|
+
/** The first day selected in the month view, so the view switcher opens the other views at it. */
|
|
2543
|
+
const [focusDay, setFocusDay] = createSignal(null);
|
|
1430
2544
|
const normalizedEvents = createMemo(() => normalizeEvents(props.events, dateConfig()));
|
|
1431
2545
|
const mergedLabels = createMemo(() => ({
|
|
1432
2546
|
today: messages().today,
|
|
@@ -1453,7 +2567,7 @@ const CalendarRoot = props => {
|
|
|
1453
2567
|
document.removeEventListener("visibilitychange", updateNow);
|
|
1454
2568
|
});
|
|
1455
2569
|
});
|
|
1456
|
-
return _$ssr(_tmpl$
|
|
2570
|
+
return _$ssr(_tmpl$63, `k2b-content-calendar ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("aria-busy", props.navigationPending ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(CalendarHeader, {
|
|
1457
2571
|
get date() {
|
|
1458
2572
|
return date();
|
|
1459
2573
|
},
|
|
@@ -1463,8 +2577,11 @@ const CalendarRoot = props => {
|
|
|
1463
2577
|
get labels() {
|
|
1464
2578
|
return mergedLabels();
|
|
1465
2579
|
},
|
|
1466
|
-
owner: owner
|
|
1467
|
-
|
|
2580
|
+
owner: owner,
|
|
2581
|
+
get focusDay() {
|
|
2582
|
+
return _$memo(() => view() === "month")() ? focusDay() : null;
|
|
2583
|
+
}
|
|
2584
|
+
})), _$escape(_$createComponent(Show, {
|
|
1468
2585
|
get when() {
|
|
1469
2586
|
return !custom();
|
|
1470
2587
|
},
|
|
@@ -1496,7 +2613,8 @@ const CalendarRoot = props => {
|
|
|
1496
2613
|
},
|
|
1497
2614
|
get labels() {
|
|
1498
2615
|
return mergedLabels();
|
|
1499
|
-
}
|
|
2616
|
+
},
|
|
2617
|
+
onFocusDay: setFocusDay
|
|
1500
2618
|
});
|
|
1501
2619
|
}
|
|
1502
2620
|
});
|