@k2b/ui 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +3 -2
  2. package/dist/browser/actions/ContextMenu.js +3 -1
  3. package/dist/browser/actions/ContextMenu.js.map +1 -1
  4. package/dist/browser/actions/Dropdown.js +91 -46
  5. package/dist/browser/actions/Dropdown.js.map +1 -1
  6. package/dist/browser/actions/FilterChip.js +4 -1
  7. package/dist/browser/actions/FilterChip.js.map +1 -1
  8. package/dist/browser/chat/ChatComposer.js +1311 -565
  9. package/dist/browser/chat/ChatComposer.js.map +1 -1
  10. package/dist/browser/chat/ChatPrimitives.js +9 -6
  11. package/dist/browser/chat/ChatPrimitives.js.map +1 -1
  12. package/dist/browser/chat/chat-behavior.js +9 -0
  13. package/dist/browser/chat/chat-behavior.js.map +1 -1
  14. package/dist/browser/chat/index.js.map +1 -1
  15. package/dist/browser/chat/types.js.map +1 -1
  16. package/dist/browser/content/Calendar.js +579 -533
  17. package/dist/browser/content/Calendar.js.map +1 -1
  18. package/dist/browser/content/FileView.js +62 -36
  19. package/dist/browser/content/FileView.js.map +1 -1
  20. package/dist/browser/content/Lightbox.js +76 -40
  21. package/dist/browser/content/Lightbox.js.map +1 -1
  22. package/dist/browser/content/MarkdownView.js +9 -3
  23. package/dist/browser/content/MarkdownView.js.map +1 -1
  24. package/dist/browser/content/PdfPreview.js +146 -79
  25. package/dist/browser/content/PdfPreview.js.map +1 -1
  26. package/dist/browser/content/Timeline.js +1038 -0
  27. package/dist/browser/content/Timeline.js.map +1 -0
  28. package/dist/browser/content/VideoPlayer.js +299 -0
  29. package/dist/browser/content/VideoPlayer.js.map +1 -0
  30. package/dist/browser/content/chart-inspection.js +7 -2
  31. package/dist/browser/content/chart-inspection.js.map +1 -1
  32. package/dist/browser/content/file-view-preview.js +2 -2
  33. package/dist/browser/content/file-view-preview.js.map +1 -1
  34. package/dist/browser/content/index.js +3 -0
  35. package/dist/browser/content/index.js.map +1 -1
  36. package/dist/browser/content/markdown-links.js +178 -0
  37. package/dist/browser/content/markdown-links.js.map +1 -0
  38. package/dist/browser/content/timeline-model.js +579 -0
  39. package/dist/browser/content/timeline-model.js.map +1 -0
  40. package/dist/browser/feedback/dialog-core.js +26 -14
  41. package/dist/browser/feedback/dialog-core.js.map +1 -1
  42. package/dist/browser/inputs/ChoiceChips.js +66 -0
  43. package/dist/browser/inputs/ChoiceChips.js.map +1 -0
  44. package/dist/browser/inputs/ChoiceGroups.js +53 -12
  45. package/dist/browser/inputs/ChoiceGroups.js.map +1 -1
  46. package/dist/browser/inputs/FileDropTarget.js +129 -0
  47. package/dist/browser/inputs/FileDropTarget.js.map +1 -0
  48. package/dist/browser/inputs/FileInputs.js +84 -43
  49. package/dist/browser/inputs/FileInputs.js.map +1 -1
  50. package/dist/browser/inputs/SignatureInput.js +558 -0
  51. package/dist/browser/inputs/SignatureInput.js.map +1 -0
  52. package/dist/browser/inputs/emoji/EmojiPicker.js +841 -0
  53. package/dist/browser/inputs/emoji/EmojiPicker.js.map +1 -0
  54. package/dist/browser/inputs/emoji/emoji-data.js +1903 -0
  55. package/dist/browser/inputs/emoji/emoji-data.js.map +1 -0
  56. package/dist/browser/inputs/emoji/emoji-index.js +209 -0
  57. package/dist/browser/inputs/emoji/emoji-index.js.map +1 -0
  58. package/dist/browser/inputs/emoji/emoji-messages.js +64 -0
  59. package/dist/browser/inputs/emoji/emoji-messages.js.map +1 -0
  60. package/dist/browser/inputs/file-drop.js +338 -0
  61. package/dist/browser/inputs/file-drop.js.map +1 -0
  62. package/dist/browser/inputs/index.js +6 -0
  63. package/dist/browser/inputs/index.js.map +1 -1
  64. package/dist/browser/inputs/markdown/MarkdownEditor.js +72 -78
  65. package/dist/browser/inputs/markdown/MarkdownEditor.js.map +1 -1
  66. package/dist/browser/inputs/markdown/actions.js +48 -2
  67. package/dist/browser/inputs/markdown/actions.js.map +1 -1
  68. package/dist/browser/inputs/markdown/code-zone.js +38 -2
  69. package/dist/browser/inputs/markdown/code-zone.js.map +1 -1
  70. package/dist/browser/inputs/signature-messages.js +39 -0
  71. package/dist/browser/inputs/signature-messages.js.map +1 -0
  72. package/dist/browser/inputs/signature.js +208 -0
  73. package/dist/browser/inputs/signature.js.map +1 -0
  74. package/dist/browser/internal/render-error.js +49 -0
  75. package/dist/browser/internal/render-error.js.map +1 -0
  76. package/dist/browser/intl/messages.js +205 -4
  77. package/dist/browser/intl/messages.js.map +1 -1
  78. package/dist/browser/layout/AppWorkspace.js +483 -218
  79. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  80. package/dist/browser/layout/FloatingWindow.js +4 -1
  81. package/dist/browser/layout/FloatingWindow.js.map +1 -1
  82. package/dist/browser/layout/Navigation.js +274 -197
  83. package/dist/browser/layout/Navigation.js.map +1 -1
  84. package/dist/browser/layout/SidebarItemPreview.js +5 -1
  85. package/dist/browser/layout/SidebarItemPreview.js.map +1 -1
  86. package/dist/browser/layout/index.js.map +1 -1
  87. package/dist/browser/layout/navigation-model.js +2 -0
  88. package/dist/browser/layout/navigation-model.js.map +1 -1
  89. package/dist/browser/surfaces/NoticeCard.js +47 -44
  90. package/dist/browser/surfaces/NoticeCard.js.map +1 -1
  91. package/dist/browser/surfaces/ResourceCard.js +212 -0
  92. package/dist/browser/surfaces/ResourceCard.js.map +1 -0
  93. package/dist/browser/surfaces/index.js +1 -0
  94. package/dist/browser/surfaces/index.js.map +1 -1
  95. package/dist/licenses/emojibase-data.txt +21 -0
  96. package/dist/ssr/actions/ContextMenu.js +3 -1
  97. package/dist/ssr/actions/ContextMenu.js.map +1 -1
  98. package/dist/ssr/actions/Dropdown.js +49 -12
  99. package/dist/ssr/actions/Dropdown.js.map +1 -1
  100. package/dist/ssr/actions/FilterChip.js +4 -1
  101. package/dist/ssr/actions/FilterChip.js.map +1 -1
  102. package/dist/ssr/chat/ChatComposer.js +823 -221
  103. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  104. package/dist/ssr/chat/ChatPrimitives.js +9 -6
  105. package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
  106. package/dist/ssr/chat/chat-behavior.js +9 -0
  107. package/dist/ssr/chat/chat-behavior.js.map +1 -1
  108. package/dist/ssr/chat/index.js.map +1 -1
  109. package/dist/ssr/chat/types.js.map +1 -1
  110. package/dist/ssr/content/Calendar.js +183 -142
  111. package/dist/ssr/content/Calendar.js.map +1 -1
  112. package/dist/ssr/content/FileView.js +30 -6
  113. package/dist/ssr/content/FileView.js.map +1 -1
  114. package/dist/ssr/content/Lightbox.js +19 -5
  115. package/dist/ssr/content/Lightbox.js.map +1 -1
  116. package/dist/ssr/content/MarkdownView.js +9 -3
  117. package/dist/ssr/content/MarkdownView.js.map +1 -1
  118. package/dist/ssr/content/PdfPreview.js +77 -36
  119. package/dist/ssr/content/PdfPreview.js.map +1 -1
  120. package/dist/ssr/content/Timeline.js +804 -0
  121. package/dist/ssr/content/Timeline.js.map +1 -0
  122. package/dist/ssr/content/VideoPlayer.js +228 -0
  123. package/dist/ssr/content/VideoPlayer.js.map +1 -0
  124. package/dist/ssr/content/chart-inspection.js +7 -2
  125. package/dist/ssr/content/chart-inspection.js.map +1 -1
  126. package/dist/ssr/content/file-view-preview.js +2 -2
  127. package/dist/ssr/content/file-view-preview.js.map +1 -1
  128. package/dist/ssr/content/index.js +3 -0
  129. package/dist/ssr/content/index.js.map +1 -1
  130. package/dist/ssr/content/markdown-links.js +178 -0
  131. package/dist/ssr/content/markdown-links.js.map +1 -0
  132. package/dist/ssr/content/timeline-model.js +579 -0
  133. package/dist/ssr/content/timeline-model.js.map +1 -0
  134. package/dist/ssr/feedback/dialog-core.js +26 -14
  135. package/dist/ssr/feedback/dialog-core.js.map +1 -1
  136. package/dist/ssr/inputs/ChoiceChips.js +66 -0
  137. package/dist/ssr/inputs/ChoiceChips.js.map +1 -0
  138. package/dist/ssr/inputs/ChoiceGroups.js +12 -4
  139. package/dist/ssr/inputs/ChoiceGroups.js.map +1 -1
  140. package/dist/ssr/inputs/FileDropTarget.js +103 -0
  141. package/dist/ssr/inputs/FileDropTarget.js.map +1 -0
  142. package/dist/ssr/inputs/FileInputs.js +49 -12
  143. package/dist/ssr/inputs/FileInputs.js.map +1 -1
  144. package/dist/ssr/inputs/SignatureInput.js +404 -0
  145. package/dist/ssr/inputs/SignatureInput.js.map +1 -0
  146. package/dist/ssr/inputs/emoji/EmojiPicker.js +551 -0
  147. package/dist/ssr/inputs/emoji/EmojiPicker.js.map +1 -0
  148. package/dist/ssr/inputs/emoji/emoji-data.js +1903 -0
  149. package/dist/ssr/inputs/emoji/emoji-data.js.map +1 -0
  150. package/dist/ssr/inputs/emoji/emoji-index.js +209 -0
  151. package/dist/ssr/inputs/emoji/emoji-index.js.map +1 -0
  152. package/dist/ssr/inputs/emoji/emoji-messages.js +64 -0
  153. package/dist/ssr/inputs/emoji/emoji-messages.js.map +1 -0
  154. package/dist/ssr/inputs/file-drop.js +338 -0
  155. package/dist/ssr/inputs/file-drop.js.map +1 -0
  156. package/dist/ssr/inputs/index.js +6 -0
  157. package/dist/ssr/inputs/index.js.map +1 -1
  158. package/dist/ssr/inputs/markdown/MarkdownEditor.js +14 -2
  159. package/dist/ssr/inputs/markdown/MarkdownEditor.js.map +1 -1
  160. package/dist/ssr/inputs/markdown/actions.js +48 -2
  161. package/dist/ssr/inputs/markdown/actions.js.map +1 -1
  162. package/dist/ssr/inputs/markdown/code-zone.js +38 -2
  163. package/dist/ssr/inputs/markdown/code-zone.js.map +1 -1
  164. package/dist/ssr/inputs/signature-messages.js +39 -0
  165. package/dist/ssr/inputs/signature-messages.js.map +1 -0
  166. package/dist/ssr/inputs/signature.js +208 -0
  167. package/dist/ssr/inputs/signature.js.map +1 -0
  168. package/dist/ssr/internal/render-error.js +49 -0
  169. package/dist/ssr/internal/render-error.js.map +1 -0
  170. package/dist/ssr/intl/messages.js +205 -4
  171. package/dist/ssr/intl/messages.js.map +1 -1
  172. package/dist/ssr/layout/AppWorkspace.js +264 -47
  173. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  174. package/dist/ssr/layout/FloatingWindow.js +4 -1
  175. package/dist/ssr/layout/FloatingWindow.js.map +1 -1
  176. package/dist/ssr/layout/Navigation.js +169 -122
  177. package/dist/ssr/layout/Navigation.js.map +1 -1
  178. package/dist/ssr/layout/SidebarItemPreview.js +5 -1
  179. package/dist/ssr/layout/SidebarItemPreview.js.map +1 -1
  180. package/dist/ssr/layout/index.js.map +1 -1
  181. package/dist/ssr/layout/navigation-model.js +2 -0
  182. package/dist/ssr/layout/navigation-model.js.map +1 -1
  183. package/dist/ssr/surfaces/NoticeCard.js +17 -12
  184. package/dist/ssr/surfaces/NoticeCard.js.map +1 -1
  185. package/dist/ssr/surfaces/ResourceCard.js +115 -0
  186. package/dist/ssr/surfaces/ResourceCard.js.map +1 -0
  187. package/dist/ssr/surfaces/index.js +1 -0
  188. package/dist/ssr/surfaces/index.js.map +1 -1
  189. package/dist/styles.css +1 -1
  190. package/dist/types/actions/Dropdown.d.ts +13 -1
  191. package/dist/types/actions/FilterChip.d.ts +5 -0
  192. package/dist/types/chat/ChatComposer.d.ts +24 -1
  193. package/dist/types/chat/ChatPrimitives.d.ts +1 -1
  194. package/dist/types/chat/chat-behavior.d.ts +15 -0
  195. package/dist/types/chat/index.d.ts +1 -1
  196. package/dist/types/chat/types.d.ts +34 -0
  197. package/dist/types/content/Calendar.d.ts +28 -8
  198. package/dist/types/content/Lightbox.d.ts +8 -0
  199. package/dist/types/content/Timeline.d.ts +49 -0
  200. package/dist/types/content/VideoPlayer.d.ts +41 -0
  201. package/dist/types/content/index.d.ts +6 -2
  202. package/dist/types/content/markdown-links.d.ts +66 -0
  203. package/dist/types/content/timeline-model.d.ts +148 -0
  204. package/dist/types/feedback/dialog-core.d.ts +1 -1
  205. package/dist/types/inputs/ChoiceChips.d.ts +20 -0
  206. package/dist/types/inputs/ChoiceGroups.d.ts +11 -2
  207. package/dist/types/inputs/FileDropTarget.d.ts +23 -0
  208. package/dist/types/inputs/FileInputs.d.ts +8 -0
  209. package/dist/types/inputs/SignatureInput.d.ts +22 -0
  210. package/dist/types/inputs/emoji/EmojiPicker.d.ts +42 -0
  211. package/dist/types/inputs/emoji/emoji-data.d.ts +2 -0
  212. package/dist/types/inputs/emoji/emoji-index.d.ts +109 -0
  213. package/dist/types/inputs/emoji/emoji-messages.d.ts +30 -0
  214. package/dist/types/inputs/file-drop.d.ts +80 -0
  215. package/dist/types/inputs/index.d.ts +12 -0
  216. package/dist/types/inputs/markdown/actions.d.ts +4 -0
  217. package/dist/types/inputs/markdown/code-zone.d.ts +7 -0
  218. package/dist/types/inputs/signature-messages.d.ts +11 -0
  219. package/dist/types/inputs/signature.d.ts +72 -0
  220. package/dist/types/internal/render-error.d.ts +13 -0
  221. package/dist/types/intl/messages.d.ts +101 -0
  222. package/dist/types/layout/AppWorkspace.d.ts +38 -0
  223. package/dist/types/layout/SidebarItemPreview.d.ts +2 -0
  224. package/dist/types/layout/index.d.ts +2 -2
  225. package/dist/types/layout/navigation-model.d.ts +18 -0
  226. package/dist/types/surfaces/ResourceCard.d.ts +31 -0
  227. package/dist/types/surfaces/index.d.ts +2 -0
  228. package/package.json +5 -4
@@ -0,0 +1,804 @@
1
+ import { ssrStyleProperty as _$ssrStyleProperty } from "solid-js/web";
2
+ import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
3
+ import { memo as _$memo } from "solid-js/web";
4
+ import { createComponent as _$createComponent } from "solid-js/web";
5
+ import { ssr as _$ssr } from "solid-js/web";
6
+ import { escape as _$escape } from "solid-js/web";
7
+ var _tmpl$ = ["<span class=\"k2b-timeline__today\">", "</span>"],
8
+ _tmpl$2 = ["<span class=\"k2b-timeline__day\">", "</span>"],
9
+ _tmpl$3 = ["<span class=\"k2b-timeline__check\"", " aria-hidden=\"true\"><i class=\"ti ti-check\"></i></span>"],
10
+ _tmpl$4 = ["<span class=\"k2b-timeline__time\">", "</span>"],
11
+ _tmpl$5 = ["<span class=\"k2b-timeline__detail\">", "</span>"],
12
+ _tmpl$6 = ["<span class=\"k2b-timeline__text\"><span class=\"k2b-timeline__label\">", "</span>", "", "</span>"],
13
+ _tmpl$7 = ["<li class=\"k2b-timeline__slot\"", " style=\"", "\">", "</li>"],
14
+ _tmpl$8 = ["<ol class=\"k2b-timeline__list\"", ">", "</ol>"],
15
+ _tmpl$9 = ["<div class=\"k2b-timeline__group\"", " style=\"", "\">", "<div class=\"k2b-timeline__decor\" aria-hidden=\"true\">", "", "", "", "</div>", "</div>"],
16
+ _tmpl$0 = ["<div class=\"k2b-timeline__fold\"", " style=\"", "\"><i class=\"ti ti-moon\"></i><span class=\"k2b-timeline__fold-label\">", "</span></div>"],
17
+ _tmpl$1 = ["<span class=\"k2b-timeline__hour\" style=\"", "\"><span class=\"k2b-timeline__hour-label\">", "</span></span>"],
18
+ _tmpl$10 = ["<span class=\"k2b-timeline__gap\"", " style=\"", "\">", "</span>"],
19
+ _tmpl$11 = ["<span class=\"k2b-timeline__echo\"", " style=\"", "\">", "</span>"],
20
+ _tmpl$12 = ["<section class=\"", "\"", " aria-describedby=\"", "\"", "><div class=\"k2b-timeline__viewport\"", "><div class=\"k2b-timeline__track\" style=\"", "\">", "<div class=\"k2b-timeline__now\" aria-hidden=\"true\"", " style=\"", "\"><span class=\"k2b-timeline__now-time\">", "</span></div></div></div><span id=\"", "\" class=\"k2b-sr-only\">", "</span></section>"];
21
+ import { createEffect, createMemo, createSignal, createUniqueId, For, Index, on, onCleanup, onMount, Show, untrack } from "solid-js";
22
+ import { Dynamic, isServer } from "solid-js/web";
23
+ import Dropdown from "../actions/Dropdown";
24
+ import { announce } from "../feedback/announce";
25
+ import { useDateConfigLocale } from "../intl/locale";
26
+ import { useUiMessages } from "../intl/messages";
27
+ import { buildTimelineModel, parseTimelineTime, spanCss, spanPx, timelineDayKey, timelineHourOf } from "./timeline-model";
28
+ /** Unit sizes of the horizontal axis, as in the stylesheet; the server and the first render use them. */
29
+ const HORIZONTAL_UNITS = [54, 48, 160, 0, 0];
30
+ /** How far beyond the visible area the first render reaches, in pixels. */
31
+ const INITIAL_REACH = 3200;
32
+ /** A scroll rests once this many frames passed without a scroll event and no finger is down. */
33
+ const REST_FRAMES = 8;
34
+ /** A pause between wheel events that starts a new gesture. */
35
+ const GESTURE_GAP_MS = 150;
36
+ /** Wheel steps from this size on, such as a mouse wheel's notches, glide; smaller ones, such as a trackpad's, apply at once. */
37
+ const GLIDE_FROM = 50;
38
+ const COLOR_TOKENS = new Set(["blue", "emerald", "amber", "red", "violet", "cyan", "zinc"]);
39
+ const HEX_COLOR = /^#[0-9a-f]{3,8}$/i;
40
+ const reducedMotion = () => typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
41
+ const colorAttributes = color => color && HEX_COLOR.test(color) ? {
42
+ token: undefined,
43
+ accent: color
44
+ } : {
45
+ token: color && COLOR_TOKENS.has(color) ? color : "blue",
46
+ accent: undefined
47
+ };
48
+ const isAllDay = entry => entry.kind === "all-day" || entry.kind === "more" && entry.area === "all-day";
49
+ const asItem = entry => entry.kind === "more" ? undefined : entry;
50
+ const asMore = entry => entry.kind === "more" ? entry : undefined;
51
+
52
+ /** The nearest ancestor that scrolls vertically, the page included. */
53
+ const verticalScroller = from => {
54
+ for (let element = from.parentElement; element; element = element.parentElement) {
55
+ if (element.scrollHeight <= element.clientHeight + 1) continue;
56
+ const overflow = getComputedStyle(element).overflowY;
57
+ if (overflow === "auto" || overflow === "scroll" || element === document.scrollingElement) return element;
58
+ }
59
+ const root = document.scrollingElement;
60
+ return root && root.scrollHeight > root.clientHeight + 1 ? root : undefined;
61
+ };
62
+
63
+ /**
64
+ * A continuous filmstrip of time: waking hours are proportional, nights and empty days fold, overlapping bands share
65
+ * lanes. Wide containers show it horizontally, narrow ones vertically; both use the same model.
66
+ */
67
+ export default function Timeline(props) {
68
+ const messages = useUiMessages();
69
+ const context = useDateConfigLocale(() => ({
70
+ ...props.dateConfig,
71
+ timeZone: props.timeZone ?? props.dateConfig?.timeZone
72
+ }));
73
+ const baseId = createUniqueId().replace(/[^a-zA-Z0-9_-]/g, "-");
74
+ const [clock, setClock] = createSignal(Date.now());
75
+ const now = createMemo(() => {
76
+ const fixed = parseTimelineTime(props.now, context());
77
+ return Number.isFinite(fixed) ? fixed : clock();
78
+ });
79
+ const today = createMemo(() => timelineDayKey(now(), context()));
80
+ const latest = createMemo(() => buildTimelineModel({
81
+ from: parseTimelineTime(props.from, context()),
82
+ to: parseTimelineTime(props.to, context()),
83
+ today: today(),
84
+ items: props.items,
85
+ context: context()
86
+ }));
87
+ /** The model on screen: the latest one, unless that one waits for the reader's scroll to rest (see `resting`). */
88
+ const [model, setModel] = createSignal(untrack(latest));
89
+ const groupsByKey = createMemo(() => new Map(model().groups.map(group => [group.key, group])));
90
+
91
+ // Formatting.
92
+ const locale = () => context().locale ?? "en";
93
+ const timeFormat = createMemo(() => new Intl.DateTimeFormat(locale(), {
94
+ hour: "2-digit",
95
+ minute: "2-digit",
96
+ hourCycle: "h23",
97
+ timeZone: context().timeZone
98
+ }));
99
+ const formatTime = time => timeFormat().format(time);
100
+ const dayFormat = createMemo(() => new Intl.DateTimeFormat(locale(), {
101
+ weekday: "short",
102
+ day: "numeric",
103
+ month: "short",
104
+ timeZone: context().timeZone
105
+ }));
106
+ const longDayFormat = createMemo(() => new Intl.DateTimeFormat(locale(), {
107
+ weekday: "long",
108
+ day: "numeric",
109
+ month: "long",
110
+ timeZone: context().timeZone
111
+ }));
112
+ const hoursFormat = createMemo(() => new Intl.NumberFormat(locale(), {
113
+ style: "unit",
114
+ unit: "hour",
115
+ unitDisplay: "short",
116
+ maximumFractionDigits: 1
117
+ }));
118
+ const noonOf = key => new Date(timelineHourOf(key, 12, context()));
119
+ const dayRange = (first, last, format) => first === last ? format.format(noonOf(first)) : `${format.format(noonOf(first))} – ${format.format(noonOf(last))}`;
120
+ const nightLabel = createMemo(() => {
121
+ const format = new Intl.DateTimeFormat(locale(), {
122
+ hour: "2-digit",
123
+ minute: "2-digit",
124
+ hourCycle: "h23",
125
+ timeZone: "UTC"
126
+ });
127
+ return messages().timelineNight({
128
+ start: format.format(Date.UTC(2000, 0, 1, 22)),
129
+ end: format.format(Date.UTC(2000, 0, 1, 6))
130
+ });
131
+ });
132
+ const spoken = entry => {
133
+ const item = entry.item;
134
+ const parts = [item.label];
135
+ if (entry.kind === "all-day") parts.push(messages().allDay);else if (entry.end > entry.start) parts.push(messages().timelineTimeRange({
136
+ start: formatTime(entry.start),
137
+ end: formatTime(entry.end)
138
+ }));else parts.push(formatTime(entry.start));
139
+ if (item.detail) parts.push(item.detail);
140
+ parts.push(entry.kind === "all-day" ? dayRange(entry.firstKey, entry.lastKey, longDayFormat()) : longDayFormat().format(new Date(entry.start)));
141
+ if (item.checked !== undefined) parts.push(item.checked ? messages().timelineDone : messages().timelineOpen);
142
+ return parts.join(", ");
143
+ };
144
+ /** When an item happens, for a "+n" menu row. */
145
+ const when = entry => {
146
+ if (entry.kind === "all-day") return `${messages().allDay}, ${dayRange(entry.firstKey, entry.lastKey, dayFormat())}`;
147
+ return entry.end > entry.start ? `${formatTime(entry.start)}–${formatTime(entry.end)}` : formatTime(entry.start);
148
+ };
149
+ const isPast = entry => {
150
+ if (entry.kind === "more") return entry.hidden.every(isPast);
151
+ if (entry.kind === "all-day") return entry.lastKey < today();
152
+ return entry.end <= now();
153
+ };
154
+
155
+ // Layout of the axis the stylesheet chose, read from the viewport.
156
+ let root;
157
+ let viewport;
158
+ let track;
159
+ const [layout, setLayout] = createSignal({
160
+ axis: "horizontal",
161
+ units: HORIZONTAL_UNITS
162
+ });
163
+ const readLayout = () => {
164
+ if (!viewport) return layout();
165
+ const style = getComputedStyle(viewport);
166
+ const unit = name => Number.parseFloat(style.getPropertyValue(`--k2b-timeline-${name}`)) || 0;
167
+ const units = [unit("hour"), unit("night"), unit("fold"), unit("head"), unit("row")];
168
+ return {
169
+ axis: style.getPropertyValue("--k2b-timeline-axis").trim() === "vertical" ? "vertical" : "horizontal",
170
+ units: units.some(value => value > 0) ? units : HORIZONTAL_UNITS
171
+ };
172
+ };
173
+ const px = time => spanPx(model().posOf(time), layout().units);
174
+
175
+ /** The visible part of the track in its own pixels, clipped by the viewport and the window. */
176
+ const visible = () => {
177
+ if (!viewport || !track) return [0, INITIAL_REACH];
178
+ const port = viewport.getBoundingClientRect();
179
+ const box = track.getBoundingClientRect();
180
+ const [portStart, portEnd, boxStart, limit] = layout().axis === "horizontal" ? [port.left, port.right, box.left, window.innerWidth] : [port.top, port.bottom, box.top, window.innerHeight];
181
+ const start = Math.max(portStart, 0) - boxStart;
182
+ return [start, Math.max(start, Math.min(portEnd, limit) - boxStart)];
183
+ };
184
+ const scrollBy = (distance, behavior = "instant") => {
185
+ // In device pixels: positions are sums of unit sizes, and an engine that cuts 4671.9999 to 4671 would move the view.
186
+ const delta = Math.round(distance * window.devicePixelRatio) / window.devicePixelRatio;
187
+ if (!viewport || Math.abs(delta) < 0.5) return;
188
+ const horizontal = layout().axis === "horizontal";
189
+ const scrollable = horizontal ? viewport.scrollWidth > viewport.clientWidth : viewport.scrollHeight > viewport.clientHeight;
190
+ if (scrollable) viewport.scrollBy({
191
+ [horizontal ? "left" : "top"]: delta,
192
+ behavior
193
+ });else if (!horizontal) verticalScroller(viewport)?.scrollBy({
194
+ top: delta,
195
+ behavior
196
+ });
197
+ };
198
+
199
+ // Bounded rendering: groups whose reach meets the visible time range, widened by a screen, plus the focused group.
200
+ const [range, setRange] = createSignal();
201
+ const shownRange = () => range() ?? [Number.NEGATIVE_INFINITY, model().timeAt(INITIAL_REACH, HORIZONTAL_UNITS)];
202
+ const [active, setActive] = createSignal();
203
+ /** The item happening now or next. */
204
+ const current = () => {
205
+ const {
206
+ order,
207
+ entries
208
+ } = model();
209
+ return order.find(id => {
210
+ const entry = entries.get(id);
211
+ return !isAllDay(entry) && entry.end >= now();
212
+ });
213
+ };
214
+ const defaultActive = createMemo(() => current() ?? model().order.at(-1));
215
+ const activeId = () => {
216
+ const id = active();
217
+ return id !== undefined && model().entries.has(id) ? id : defaultActive();
218
+ };
219
+ const renderedKeys = createMemo(() => {
220
+ const [low, high] = shownRange();
221
+ const focusGroup = model().entries.get(activeId() ?? "")?.group;
222
+ return model().groups.filter(group => group.index === focusGroup || group.reach[1] >= low && group.reach[0] <= high).map(group => group.key);
223
+ });
224
+
225
+ // Reading position: the time at the start edge stays in place when days load or the axis changes.
226
+ /** The time at the start edge after the last refresh, for an axis change. */
227
+ let edgeTime;
228
+ /**
229
+ * The time to hold in place. A load at the start spreads a leading night or fold over other times, so where the start
230
+ * edge lies in one, the end of that segment holds instead, and with it everything after.
231
+ */
232
+ const anchorAt = (built, start, units) => {
233
+ const time = built.timeAt(start, units);
234
+ const first = built.segments.find(segment => segment.t1 > segment.t0);
235
+ return first && first.kind !== "wake" && time < first.t1 ? first.t1 : time;
236
+ };
237
+ /** How far what the reader sees moves from one model to the next. */
238
+ const shiftOf = (before, after) => {
239
+ const {
240
+ units
241
+ } = layout();
242
+ const time = anchorAt(before, visible()[0], units);
243
+ return spanPx(after.posOf(time), units) - spanPx(before.posOf(time), units);
244
+ };
245
+ /**
246
+ * Where each end of the range was, and how long the strip was, when that end was last asked for. An end is asked
247
+ * again only once the range moved there and the strip's length changed: days that fold into the fold at that end
248
+ * leave the reader where they were, and asking again would load an empty calendar week after week on its own.
249
+ */
250
+ let requestedFrom;
251
+ let requestedTo;
252
+ let pendingEarlier = false;
253
+ let pendingLater = false;
254
+ const loadEdge = edge => {
255
+ const load = edge === "earlier" ? props.onLoadEarlier : props.onLoadLater;
256
+ if (!load) return;
257
+ const settle = () => {
258
+ if (edge === "earlier") pendingEarlier = false;else pendingLater = false;
259
+ queueMicrotask(refresh);
260
+ };
261
+ if (edge === "earlier") pendingEarlier = true;else pendingLater = true;
262
+ Promise.resolve().then(load).then(settle, settle);
263
+ };
264
+ const refresh = () => {
265
+ if (!viewport) return;
266
+ const built = model();
267
+ const {
268
+ units
269
+ } = layout();
270
+ const [start, end] = visible();
271
+ const reach = Math.max(end - start, 600);
272
+ edgeTime = built.timeAt(start, units);
273
+ const [low, high] = range() ?? [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY];
274
+ if (built.timeAt(start - reach / 2, units) < low || built.timeAt(end + reach / 2, units) > high) setRange([built.timeAt(start - reach, units), built.timeAt(end + reach, units)]);
275
+ const length = spanPx(built.total, units);
276
+ const due = (requested, at) => requested === undefined || requested.at !== at && requested.length !== length;
277
+ if (start > reach) requestedFrom = undefined;else if (!pendingEarlier && due(requestedFrom, built.from)) {
278
+ requestedFrom = {
279
+ at: built.from,
280
+ length
281
+ };
282
+ loadEdge("earlier");
283
+ }
284
+ if (end < length - reach) requestedTo = undefined;else if (!pendingLater && due(requestedTo, built.to)) {
285
+ requestedTo = {
286
+ at: built.to,
287
+ length
288
+ };
289
+ loadEdge("later");
290
+ }
291
+ };
292
+ const scrollToTime = (time, align = "start") => {
293
+ if (!viewport) return;
294
+ const [start, end] = visible();
295
+ const target = px(time) - (align === "center" ? (end - start) / 2 : 16);
296
+ scrollBy(target - start, ownScrollBehavior());
297
+ };
298
+
299
+ // Keyboard: one tab stop, moved by the keys of the axis.
300
+ const elementOf = id => [...(viewport?.querySelectorAll("[data-entry-id]") ?? [])].find(element => element.dataset.entryId === id);
301
+ const focusEntry = (id, message) => {
302
+ if (id === undefined) return;
303
+ setActive(id);
304
+ const element = elementOf(id);
305
+ element?.focus({
306
+ preventScroll: true
307
+ });
308
+ element?.scrollIntoView?.({
309
+ block: "nearest",
310
+ inline: "nearest",
311
+ behavior: ownScrollBehavior()
312
+ });
313
+ if (message) announce(message);
314
+ };
315
+ const groupLabel = group => dayRange(group.firstKey, group.lastKey, longDayFormat());
316
+ const onKeyDown = event => {
317
+ const target = event.target;
318
+ if (!target || target.closest("[role='menu']") || event.altKey || event.ctrlKey || event.metaKey) return;
319
+ const id = target.closest("[data-entry-id]")?.dataset.entryId;
320
+ const {
321
+ order,
322
+ entries,
323
+ groups
324
+ } = model();
325
+ const index = id === undefined ? -1 : order.indexOf(id);
326
+ const entry = id === undefined ? undefined : entries.get(id);
327
+ const [back, forward] = layout().axis === "horizontal" ? ["ArrowLeft", "ArrowRight"] : ["ArrowUp", "ArrowDown"];
328
+ const key = event.key;
329
+ const handled = () => {
330
+ event.preventDefault();
331
+ event.stopPropagation();
332
+ };
333
+ if (key.toLowerCase() === "t") {
334
+ handled();
335
+ const happening = current();
336
+ if (happening) focusEntry(happening);else scrollToTime(now(), "center");
337
+ announce(messages().timelineNow({
338
+ time: formatTime(now())
339
+ }));
340
+ return;
341
+ }
342
+ if (!entry) return;
343
+ const group = groups[entry.group];
344
+ if (key === back || key === forward) {
345
+ handled();
346
+ focusEntry(order[Math.max(0, Math.min(order.length - 1, index + (key === forward ? 1 : -1)))]);
347
+ } else if (key === "PageDown" || key === "PageUp") {
348
+ handled();
349
+ const step = key === "PageDown" ? 1 : -1;
350
+ for (let next = entry.group + step; next >= 0 && next < groups.length; next += step) {
351
+ const first = groups[next].entries[0];
352
+ if (first) return focusEntry(first.id, groupLabel(groups[next]));
353
+ }
354
+ } else if (key === "Home" || key === "End") {
355
+ handled();
356
+ focusEntry((key === "Home" ? group.entries[0] : group.entries.at(-1))?.id);
357
+ } else if (key === " " && entry.kind !== "more") {
358
+ handled();
359
+ if (props.onToggle && entry.item.checked !== undefined) props.onToggle(entry.item, !entry.item.checked);else if (props.onActivate) props.onActivate(entry.item);else if (entry.item.href) target.click();
360
+ }
361
+ };
362
+
363
+ // The wheel scrolls a horizontal strip sideways only where nothing else scrolls vertically, decided once per gesture.
364
+ let wheelAt = Number.NEGATIVE_INFINITY;
365
+ let primary = false;
366
+ /** Pixels a gliding wheel still moves. Relative, so a scroll from elsewhere, such as days loading, adds to it. */
367
+ let wheelLeft = 0;
368
+ let wheelFrame = 0;
369
+ const glide = () => {
370
+ wheelFrame = 0;
371
+ if (!viewport) return;
372
+ // Whole pixels, so every frame moves until the glide is done or meets an edge.
373
+ const step = Math.abs(wheelLeft) <= 1 ? wheelLeft : Math.trunc(wheelLeft * 0.35) || Math.sign(wheelLeft);
374
+ const before = viewport.scrollLeft;
375
+ viewport.scrollLeft = before + step;
376
+ wheelLeft -= step;
377
+ if (wheelLeft !== 0 && viewport.scrollLeft !== before) wheelFrame = requestAnimationFrame(glide);else wheelLeft = 0;
378
+ };
379
+ const onWheel = event => {
380
+ if (!viewport || layout().axis !== "horizontal" || event.ctrlKey || event.shiftKey) return;
381
+ if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) return;
382
+ const time = performance.now();
383
+ if (time - wheelAt > GESTURE_GAP_MS) primary = !verticalScroller(viewport);
384
+ wheelAt = time;
385
+ if (!primary) return;
386
+ event.preventDefault();
387
+ const delta = event.deltaY * (event.deltaMode === 1 ? 40 : event.deltaMode === 2 ? viewport.clientWidth : 1);
388
+ if (reducedMotion() || Math.abs(delta) < GLIDE_FROM && !wheelFrame) {
389
+ viewport.scrollLeft += delta;
390
+ return;
391
+ }
392
+ wheelLeft += delta;
393
+ if (!wheelFrame) wheelFrame = requestAnimationFrame(glide);
394
+ };
395
+ const activate = (event, entry) => {
396
+ const onBox = event.target?.closest(".k2b-timeline__check");
397
+ if (onBox && props.onToggle && entry.item.checked !== undefined) {
398
+ event.preventDefault();
399
+ props.onToggle(entry.item, !entry.item.checked);
400
+ return;
401
+ }
402
+ // A modified click on a link keeps its browser meaning, such as opening a new tab.
403
+ if (!props.onActivate || entry.item.href && (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) return;
404
+ event.preventDefault();
405
+ props.onActivate(entry.item);
406
+ };
407
+
408
+ // While the reader scrolls, a scroll write that keeps the view in place would fight a finger or momentum, as on iOS.
409
+ // So a new model that moves what they see waits until the scroll rests: frames without a scroll event, no finger
410
+ // down. Counted in frames, as scrolling and momentum report every frame, also when a busy page draws slowly.
411
+ const [resting, setResting] = createSignal(true);
412
+ const fingers = new Set();
413
+ let quietFrames = 0;
414
+ let restFrame = 0;
415
+ const countQuiet = () => {
416
+ restFrame = 0;
417
+ if (fingers.size > 0) return;
418
+ if (++quietFrames >= REST_FRAMES) setResting(true);else restFrame = requestAnimationFrame(countQuiet);
419
+ };
420
+ const scrolled = () => {
421
+ setResting(false);
422
+ quietFrames = 0;
423
+ if (!restFrame) restFrame = requestAnimationFrame(countQuiet);
424
+ };
425
+ /**
426
+ * How a scroll the timeline starts itself moves. A smooth one reports its first frame only later, and a write that
427
+ * keeps the view in place stops it, so it counts as scrolling from its start: days that load meanwhile wait until it
428
+ * arrives instead of cutting it short.
429
+ */
430
+ const ownScrollBehavior = () => {
431
+ if (reducedMotion()) return "auto";
432
+ scrolled();
433
+ return "smooth";
434
+ };
435
+ const onTouchStart = event => {
436
+ const target = event.target;
437
+ if (!target) return;
438
+ const touches = [...event.changedTouches].map(touch => touch.identifier);
439
+ for (const touch of touches) fingers.add(touch);
440
+ setResting(false);
441
+ // Touch events keep going to where they began, also after that element left the timeline.
442
+ const release = ended => {
443
+ for (const touch of ended.changedTouches) fingers.delete(touch.identifier);
444
+ if (touches.some(touch => fingers.has(touch))) return;
445
+ target.removeEventListener("touchend", release);
446
+ target.removeEventListener("touchcancel", release);
447
+ scrolled();
448
+ };
449
+ target.addEventListener("touchend", release);
450
+ target.addEventListener("touchcancel", release);
451
+ };
452
+ /** The item that had focus when the model changed, to keep focus in the timeline if its element goes. */
453
+ let focused;
454
+ createEffect(() => {
455
+ const next = latest();
456
+ const rest = resting();
457
+ untrack(() => {
458
+ const shown = model();
459
+ if (next === shown || !rest && viewport && Math.abs(shiftOf(shown, next)) >= 0.5) return;
460
+ focused = root?.contains(document.activeElement) && !document.activeElement?.closest("[role='menu']") ? activeId() : undefined;
461
+ setModel(next);
462
+ });
463
+ });
464
+ /** The entry that stands in for `id` in the next model: itself, the "+n" entry that hides it, or its nearest neighbour. */
465
+ const successor = (before, after, id) => {
466
+ if (after.entries.has(id)) return id;
467
+ for (const entry of after.entries.values()) if (entry.kind === "more" && entry.hidden.some(hidden => hidden.id === id)) return entry.id;
468
+ const index = before.order.indexOf(id);
469
+ return [...before.order.slice(index + 1), ...before.order.slice(0, Math.max(0, index)).reverse()].find(other => after.entries.has(other));
470
+ };
471
+ // Days loading at either end, and items changing, keep what the reader sees in place, and keep focus.
472
+ createEffect(on(model, (current, previous) => {
473
+ if (!previous) return;
474
+ scrollBy(shiftOf(previous, current));
475
+ const id = focused === undefined ? undefined : successor(previous, current, focused);
476
+ focused = undefined;
477
+ if (id !== undefined && !root?.contains(document.activeElement)) {
478
+ setActive(id);
479
+ // A day that only now holds the tab stop renders right after this update.
480
+ const focus = () => elementOf(id)?.focus({
481
+ preventScroll: true
482
+ });
483
+ if (!elementOf(id)) queueMicrotask(focus);else focus();
484
+ }
485
+ refresh();
486
+ }));
487
+ onMount(() => {
488
+ if (isServer || !viewport || !root) return;
489
+ setLayout(readLayout());
490
+ refresh();
491
+ let frame = 0;
492
+ const schedule = () => {
493
+ if (frame) return;
494
+ frame = requestAnimationFrame(() => {
495
+ frame = 0;
496
+ refresh();
497
+ });
498
+ };
499
+ // Scrolls of the strip and of the areas around it, which move it too.
500
+ const onScroll = event => {
501
+ const target = event.target;
502
+ if (target !== document && !(target instanceof Node && viewport && target.contains(viewport))) return;
503
+ scrolled();
504
+ schedule();
505
+ };
506
+ const resize = new ResizeObserver(() => {
507
+ const next = readLayout();
508
+ const previous = layout();
509
+ if (next.axis !== previous.axis || next.units.some((value, index) => value !== previous.units[index])) {
510
+ setLayout(next);
511
+ if (edgeTime !== undefined) scrollBy(px(edgeTime) - visible()[0]);
512
+ refresh();
513
+ } else schedule();
514
+ });
515
+ resize.observe(viewport);
516
+ viewport.addEventListener("wheel", onWheel, {
517
+ passive: false
518
+ });
519
+ viewport.addEventListener("touchstart", onTouchStart, {
520
+ passive: true
521
+ });
522
+ root.addEventListener("keydown", onKeyDown, true);
523
+ window.addEventListener("scroll", onScroll, {
524
+ passive: true,
525
+ capture: true
526
+ });
527
+ const timer = props.now === undefined ? setInterval(() => setClock(Date.now()), 30_000) : undefined;
528
+ onCleanup(() => {
529
+ resize.disconnect();
530
+ viewport?.removeEventListener("wheel", onWheel);
531
+ viewport?.removeEventListener("touchstart", onTouchStart);
532
+ root?.removeEventListener("keydown", onKeyDown, true);
533
+ window.removeEventListener("scroll", onScroll, {
534
+ capture: true
535
+ });
536
+ cancelAnimationFrame(frame);
537
+ cancelAnimationFrame(wheelFrame);
538
+ cancelAnimationFrame(restFrame);
539
+ if (timer) clearInterval(timer);
540
+ });
541
+ props.controller?.({
542
+ scrollToTime: (time, options) => scrollToTime(parseTimelineTime(time, context()), options?.align),
543
+ scrollToNow: () => scrollToTime(now(), "center")
544
+ });
545
+ });
546
+ const nowShown = () => now() >= model().from && now() < model().to;
547
+ const Heading = headingProps => _$createComponent(Dynamic, {
548
+ get component() {
549
+ return `h${props.headingLevel ?? 3}`;
550
+ },
551
+ get id() {
552
+ return `${baseId}-${headingProps.group.key.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
553
+ },
554
+ "class": "k2b-timeline__heading",
555
+ get children() {
556
+ return [_$createComponent(Show, {
557
+ get when() {
558
+ return headingProps.group.today;
559
+ },
560
+ get children() {
561
+ return _$ssr(_tmpl$, _$escape(messages().today));
562
+ }
563
+ }), _$ssr(_tmpl$2, _$escape(dayRange(headingProps.group.firstKey, headingProps.group.lastKey, dayFormat())))];
564
+ }
565
+ });
566
+ const ItemControl = itemProps => {
567
+ const entry = () => itemProps.entry;
568
+ const name = createMemo(() => spoken(entry()));
569
+ const time = () => {
570
+ const current = entry();
571
+ if (current.kind === "all-day") return "";
572
+ if (current.kind !== "folded" && current.end > current.start) return `${formatTime(current.start)}–${formatTime(current.end)}`;
573
+ return formatTime(current.start);
574
+ };
575
+ return _$createComponent(Dynamic, {
576
+ get component() {
577
+ return entry().item.href ? "a" : "button";
578
+ },
579
+ get type() {
580
+ return entry().item.href ? undefined : "button";
581
+ },
582
+ get href() {
583
+ return entry().item.href;
584
+ },
585
+ "class": "k2b-timeline__item",
586
+ get ["data-entry-id"]() {
587
+ return entry().id;
588
+ },
589
+ get tabindex() {
590
+ return activeId() === entry().id ? 0 : -1;
591
+ },
592
+ get ["aria-label"]() {
593
+ return name();
594
+ },
595
+ get title() {
596
+ return name();
597
+ },
598
+ onClick: event => activate(event, entry()),
599
+ get children() {
600
+ return [_$createComponent(Show, {
601
+ get when() {
602
+ return entry().item.checked !== undefined;
603
+ },
604
+ get children() {
605
+ return _$ssr(_tmpl$3, _$ssrAttribute("data-checked", entry().item.checked ? "true" : _$escape(undefined, true), false));
606
+ }
607
+ }), _$ssr(_tmpl$6, _$escape(entry().item.label), _$escape(_$createComponent(Show, {
608
+ get when() {
609
+ return time();
610
+ },
611
+ get children() {
612
+ return _$ssr(_tmpl$4, _$escape(time()));
613
+ }
614
+ })), _$escape(_$createComponent(Show, {
615
+ get when() {
616
+ return entry().item.detail;
617
+ },
618
+ get children() {
619
+ return _$ssr(_tmpl$5, _$escape(entry().item.detail));
620
+ }
621
+ })))];
622
+ }
623
+ });
624
+ };
625
+ const MoreControl = moreProps => {
626
+ const entry = () => moreProps.entry;
627
+ const label = () => {
628
+ const current = entry();
629
+ const count = messages().timelineMore({
630
+ count: current.hidden.length
631
+ });
632
+ return current.area === "all-day" ? count : `${count}, ${messages().timelineTimeRange({
633
+ start: formatTime(current.start),
634
+ end: formatTime(current.end)
635
+ })}`;
636
+ };
637
+ // A row opens its item as a click on it does. Where only the checkbox may change, the row is that checkbox.
638
+ const items = () => entry().hidden.map(hidden => {
639
+ const {
640
+ item
641
+ } = hidden;
642
+ const parts = [when(hidden), ...(item.detail ? [item.detail] : [])];
643
+ const state = item.checked === undefined ? [] : [item.checked ? messages().timelineDone : messages().timelineOpen];
644
+ const icon = item.checked === undefined ? undefined : item.checked ? "ti ti-circle-check" : "ti ti-circle";
645
+ const base = {
646
+ label: item.label,
647
+ description: [...parts, ...state].join(", "),
648
+ icon
649
+ };
650
+ const open = props.onActivate;
651
+ if (open) return item.href ? {
652
+ ...base,
653
+ href: item.href,
654
+ action: () => open(item)
655
+ } : {
656
+ ...base,
657
+ action: () => open(item)
658
+ };
659
+ if (item.href) return {
660
+ ...base,
661
+ href: item.href
662
+ };
663
+ const toggle = props.onToggle;
664
+ if (toggle && item.checked !== undefined) {
665
+ const checked = item.checked;
666
+ return {
667
+ label: item.label,
668
+ description: parts.join(", "),
669
+ choice: "checkbox",
670
+ checked,
671
+ action: () => toggle(item, !checked)
672
+ };
673
+ }
674
+ return {
675
+ ...base,
676
+ disabled: true
677
+ };
678
+ });
679
+ return _$createComponent(Dropdown.Root, {
680
+ get items() {
681
+ return items();
682
+ },
683
+ get label() {
684
+ return label();
685
+ },
686
+ "class": "k2b-timeline__more-root",
687
+ get children() {
688
+ return _$createComponent(Dropdown.Trigger, {
689
+ appearance: "plain",
690
+ "class": "k2b-timeline__item k2b-timeline__more",
691
+ get ["data-entry-id"]() {
692
+ return entry().id;
693
+ },
694
+ get tabIndex() {
695
+ return activeId() === entry().id ? 0 : -1;
696
+ },
697
+ get label() {
698
+ return label();
699
+ },
700
+ get title() {
701
+ return label();
702
+ },
703
+ get children() {
704
+ return ["+", entry().hidden.length];
705
+ }
706
+ });
707
+ }
708
+ });
709
+ };
710
+ const Slot = slotProps => {
711
+ const entry = createMemo(previous => model().entries.get(slotProps.id) ?? previous, model().entries.get(slotProps.id));
712
+ const color = () => {
713
+ const current = entry();
714
+ return colorAttributes(current.kind === "more" ? undefined : current.item.color);
715
+ };
716
+ const span = () => {
717
+ const current = entry();
718
+ if (current.kind !== "band") return undefined;
719
+ const minutes = (current.end - current.start) / 60_000;
720
+ return minutes < 30 ? "xs" : minutes < 60 ? "s" : minutes < 120 ? "m" : "l";
721
+ };
722
+ return _$ssr(_tmpl$7, _$ssrAttribute("data-kind", _$escape(entry().kind, true), false) + _$ssrAttribute("data-area", _$escape(asMore(entry())?.area, true), false) + _$ssrAttribute("data-past", isPast(entry()) ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-span", _$escape(span(), true), false) + _$ssrAttribute("data-color", _$escape(color().token, true), false), _$ssrStyleProperty("--k2b-timeline-at:", _$escape(spanCss(entry().at), true)) + _$ssrStyleProperty(";--k2b-timeline-size:", _$escape(spanCss(entry().size), true)) + _$ssrStyleProperty(";--k2b-timeline-lane:", _$escape(String(entry().lane), true)) + _$ssrStyleProperty(";--k2b-timeline-lanes:", _$escape(String(entry().lanes), true)) + _$ssrStyleProperty(";--k2b-timeline-accent:", _$escape(color().accent, true)), _$escape(_$createComponent(Show, {
723
+ get when() {
724
+ return asMore(entry());
725
+ },
726
+ get fallback() {
727
+ return _$createComponent(ItemControl, {
728
+ get entry() {
729
+ return asItem(entry());
730
+ }
731
+ });
732
+ },
733
+ children: more => _$createComponent(MoreControl, {
734
+ get entry() {
735
+ return more();
736
+ }
737
+ })
738
+ })));
739
+ };
740
+ const Group = groupProps => {
741
+ const group = createMemo(previous => groupsByKey().get(groupProps.key) ?? previous, groupsByKey().get(groupProps.key));
742
+ const headingId = () => `${baseId}-${group().key.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
743
+ return _$ssr(_tmpl$9, _$ssrAttribute("data-kind", _$escape(group().kind, true), false) + _$ssrAttribute("data-today", group().today ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-past", group().past ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-row", group().row ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("--k2b-timeline-at:", _$escape(spanCss(group().at), true)) + _$ssrStyleProperty(";--k2b-timeline-size:", _$escape(spanCss(group().size), true)), _$escape(_$createComponent(Heading, {
744
+ get group() {
745
+ return group();
746
+ }
747
+ })), _$escape(_$createComponent(Index, {
748
+ get each() {
749
+ return group().folds;
750
+ },
751
+ children: fold => _$ssr(_tmpl$0, _$ssrAttribute("data-kind", _$escape(fold().kind, true), false), _$ssrStyleProperty("--k2b-timeline-at:", _$escape(spanCss(fold().at), true)) + _$ssrStyleProperty(";--k2b-timeline-size:", _$escape(spanCss(fold().size), true)), fold().kind === "night" ? _$escape(nightLabel()) : _$escape(messages().timelineNothingPlanned))
752
+ })), _$escape(_$createComponent(Index, {
753
+ get each() {
754
+ return group().hours;
755
+ },
756
+ children: hour => _$ssr(_tmpl$1, _$ssrStyleProperty("--k2b-timeline-at:", _$escape(spanCss(hour().at), true)), _$escape(formatTime(hour().time)))
757
+ })), _$escape(_$createComponent(Index, {
758
+ get each() {
759
+ return group().gaps;
760
+ },
761
+ children: gap => _$ssr(_tmpl$10, _$ssrAttribute("data-long", gap().long ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-past", gap().end <= now() ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("--k2b-timeline-at:", _$escape(spanCss(gap().at), true)), _$escape(messages().timelineFree({
762
+ duration: hoursFormat().format(gap().hours)
763
+ })))
764
+ })), _$escape(_$createComponent(Index, {
765
+ get each() {
766
+ return group().echoes;
767
+ },
768
+ children: echo => {
769
+ const color = () => {
770
+ const current = echo();
771
+ return colorAttributes(current.kind === "more" ? undefined : current.item.color);
772
+ };
773
+ return (// A pointer shortcut to an item that the keyboard and screen readers reach on the day it starts.
774
+ // biome-ignore lint/a11y/noStaticElementInteractions: hidden from assistive technology on purpose.
775
+ // biome-ignore lint/a11y/useKeyWithClickEvents: the item itself is the keyboard target.
776
+ _$ssr(_tmpl$11, _$ssrAttribute("data-color", _$escape(color().token, true), false) + _$ssrAttribute("data-past", isPast(echo()) ? "true" : _$escape(undefined, true), false), _$ssrStyleProperty("--k2b-timeline-lane:", _$escape(String(echo().lane), true)) + _$ssrStyleProperty(";--k2b-timeline-accent:", _$escape(color().accent, true)), _$escape(asItem(echo())?.item.label ?? `+${asMore(echo())?.hidden.length ?? 0}`))
777
+ );
778
+ }
779
+ })), _$escape(_$createComponent(Show, {
780
+ get when() {
781
+ return group().entries.length > 0;
782
+ },
783
+ get children() {
784
+ return _$ssr(_tmpl$8, _$ssrAttribute("aria-labelledby", _$escape(headingId(), true), false), _$escape(_$createComponent(For, {
785
+ get each() {
786
+ return group().entries.map(entry => entry.id);
787
+ },
788
+ children: id => _$createComponent(Slot, {
789
+ id: id
790
+ })
791
+ })));
792
+ }
793
+ })));
794
+ };
795
+ return _$ssr(_tmpl$12, `k2b-timeline ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("aria-label", _$escape(props.label ?? messages().timeline, true), false), `${_$escape(baseId, true)}-hint`, _$ssrAttribute("aria-busy", props.busy ? "true" : _$escape(undefined, true), false), _$ssrAttribute("tabindex", model().order.length === 0 ? 0 : _$escape(undefined, true), false), _$ssrStyleProperty("--k2b-timeline-length:", _$escape(spanCss(model().total), true)), _$escape(_$createComponent(For, {
796
+ get each() {
797
+ return renderedKeys();
798
+ },
799
+ children: key => _$createComponent(Group, {
800
+ key: key
801
+ })
802
+ })), _$ssrAttribute("data-hidden", nowShown() ? _$escape(undefined, true) : "true", false), _$ssrStyleProperty("--k2b-timeline-at:", _$escape(spanCss(model().posOf(now())), true)), _$escape(formatTime(now())), `${_$escape(baseId, true)}-hint`, _$escape(messages().timelineHint));
803
+ }
804
+ //# sourceMappingURL=Timeline.js.map