@k2b/ui 0.32.0 → 0.34.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 (292) hide show
  1. package/dist/base.css +14 -1
  2. package/dist/browser/actions/ContextMenu.js +13 -0
  3. package/dist/browser/actions/ContextMenu.js.map +1 -1
  4. package/dist/browser/actions/Dropdown.js +173 -102
  5. package/dist/browser/actions/Dropdown.js.map +1 -1
  6. package/dist/browser/actions/FilterChip.js +113 -56
  7. package/dist/browser/actions/FilterChip.js.map +1 -1
  8. package/dist/browser/actions/SelectionToolbar.js +129 -0
  9. package/dist/browser/actions/SelectionToolbar.js.map +1 -0
  10. package/dist/browser/actions/filter-messages.js +26 -2
  11. package/dist/browser/actions/filter-messages.js.map +1 -1
  12. package/dist/browser/actions/index.js +1 -0
  13. package/dist/browser/actions/index.js.map +1 -1
  14. package/dist/browser/actions/menu-rows.js +23 -3
  15. package/dist/browser/actions/menu-rows.js.map +1 -1
  16. package/dist/browser/actions/menu-sheet.js +2 -7
  17. package/dist/browser/actions/menu-sheet.js.map +1 -1
  18. package/dist/browser/charts.js +6 -0
  19. package/dist/browser/charts.js.map +1 -0
  20. package/dist/browser/chat/ChatComposer.js +616 -421
  21. package/dist/browser/chat/ChatComposer.js.map +1 -1
  22. package/dist/browser/chat/chat-messages.js +18 -0
  23. package/dist/browser/chat/chat-messages.js.map +1 -1
  24. package/dist/browser/chat/composer-document.js +19 -0
  25. package/dist/browser/chat/composer-document.js.map +1 -1
  26. package/dist/browser/chat/index.js.map +1 -1
  27. package/dist/browser/content/Calendar.js +39 -29
  28. package/dist/browser/content/Calendar.js.map +1 -1
  29. package/dist/browser/content/CalendarDayView.js +159 -165
  30. package/dist/browser/content/CalendarDayView.js.map +1 -1
  31. package/dist/browser/content/Chart.js +8 -0
  32. package/dist/browser/content/Chart.js.map +1 -1
  33. package/dist/browser/content/FileGrid.js +231 -9
  34. package/dist/browser/content/FileGrid.js.map +1 -1
  35. package/dist/browser/content/FileTypeIcon.js +15 -0
  36. package/dist/browser/content/FileTypeIcon.js.map +1 -1
  37. package/dist/browser/content/FileView.js +11 -4
  38. package/dist/browser/content/FileView.js.map +1 -1
  39. package/dist/browser/content/MessageRow.js +627 -343
  40. package/dist/browser/content/MessageRow.js.map +1 -1
  41. package/dist/browser/content/ReactionBar.js +20 -1
  42. package/dist/browser/content/ReactionBar.js.map +1 -1
  43. package/dist/browser/content/calendar-day-scroll.js +4 -3
  44. package/dist/browser/content/calendar-day-scroll.js.map +1 -1
  45. package/dist/browser/content/chart-bar.js +476 -0
  46. package/dist/browser/content/chart-bar.js.map +1 -0
  47. package/dist/browser/content/file-list-messages.js +52 -0
  48. package/dist/browser/content/file-list-messages.js.map +1 -0
  49. package/dist/browser/content/index.js +2 -2
  50. package/dist/browser/content/index.js.map +1 -1
  51. package/dist/browser/feedback/Tooltip.js +31 -14
  52. package/dist/browser/feedback/Tooltip.js.map +1 -1
  53. package/dist/browser/feedback/dialog-core.js +6 -0
  54. package/dist/browser/feedback/dialog-core.js.map +1 -1
  55. package/dist/browser/inputs/ChoiceList.js +178 -0
  56. package/dist/browser/inputs/ChoiceList.js.map +1 -0
  57. package/dist/browser/inputs/ChoiceSlider.js +314 -0
  58. package/dist/browser/inputs/ChoiceSlider.js.map +1 -0
  59. package/dist/browser/inputs/ChoiceTiles.js +135 -0
  60. package/dist/browser/inputs/ChoiceTiles.js.map +1 -0
  61. package/dist/browser/inputs/ColorChoices.js +120 -0
  62. package/dist/browser/inputs/ColorChoices.js.map +1 -0
  63. package/dist/browser/inputs/QrScanner.js +95 -13
  64. package/dist/browser/inputs/QrScanner.js.map +1 -1
  65. package/dist/browser/inputs/TextInput.js +19 -14
  66. package/dist/browser/inputs/TextInput.js.map +1 -1
  67. package/dist/browser/inputs/index.js +4 -0
  68. package/dist/browser/inputs/index.js.map +1 -1
  69. package/dist/browser/inputs/qr-messages.js +2 -0
  70. package/dist/browser/inputs/qr-messages.js.map +1 -1
  71. package/dist/browser/internal/radio-keys.js +13 -0
  72. package/dist/browser/internal/radio-keys.js.map +1 -0
  73. package/dist/browser/intl/shortcut-messages.js +4 -0
  74. package/dist/browser/intl/shortcut-messages.js.map +1 -1
  75. package/dist/browser/intl/shortcut.js +4 -0
  76. package/dist/browser/intl/shortcut.js.map +1 -1
  77. package/dist/browser/layout/AnchoredPopover.js +180 -0
  78. package/dist/browser/layout/AnchoredPopover.js.map +1 -0
  79. package/dist/browser/layout/AppWorkspace.js +684 -532
  80. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  81. package/dist/browser/layout/DetailPanel.js +443 -333
  82. package/dist/browser/layout/DetailPanel.js.map +1 -1
  83. package/dist/browser/layout/MobileShell.js +231 -42
  84. package/dist/browser/layout/MobileShell.js.map +1 -1
  85. package/dist/browser/layout/Settings.js +283 -175
  86. package/dist/browser/layout/Settings.js.map +1 -1
  87. package/dist/browser/layout/SidebarRailHint.js +79 -0
  88. package/dist/browser/layout/SidebarRailHint.js.map +1 -0
  89. package/dist/browser/layout/SidebarSwitcher.js +79 -66
  90. package/dist/browser/layout/SidebarSwitcher.js.map +1 -1
  91. package/dist/browser/layout/TabBar.js +18 -4
  92. package/dist/browser/layout/TabBar.js.map +1 -1
  93. package/dist/browser/layout/app-workspace-controller.js +11 -4
  94. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  95. package/dist/browser/layout/detail-panel-messages.js +4 -2
  96. package/dist/browser/layout/detail-panel-messages.js.map +1 -1
  97. package/dist/browser/layout/index.js +3 -1
  98. package/dist/browser/layout/index.js.map +1 -1
  99. package/dist/browser/layout/scroll-fade.js +32 -5
  100. package/dist/browser/layout/scroll-fade.js.map +1 -1
  101. package/dist/browser/layout/settings-messages.js +26 -2
  102. package/dist/browser/layout/settings-messages.js.map +1 -1
  103. package/dist/browser/surfaces/Avatar.js +2 -14
  104. package/dist/browser/surfaces/Avatar.js.map +1 -1
  105. package/dist/browser/surfaces/DotStrip.js +70 -0
  106. package/dist/browser/surfaces/DotStrip.js.map +1 -0
  107. package/dist/browser/surfaces/NoticeCard.js +46 -30
  108. package/dist/browser/surfaces/NoticeCard.js.map +1 -1
  109. package/dist/browser/surfaces/ProgressRing.js +3 -0
  110. package/dist/browser/surfaces/ProgressRing.js.map +1 -1
  111. package/dist/browser/surfaces/ResourceCard.js +125 -68
  112. package/dist/browser/surfaces/ResourceCard.js.map +1 -1
  113. package/dist/browser/surfaces/index.js +2 -0
  114. package/dist/browser/surfaces/index.js.map +1 -1
  115. package/dist/browser/surfaces/person-color.js +18 -0
  116. package/dist/browser/surfaces/person-color.js.map +1 -0
  117. package/dist/browser/surfaces/resource-messages.js +8 -4
  118. package/dist/browser/surfaces/resource-messages.js.map +1 -1
  119. package/dist/browser/widgets/Widget.js +3 -2
  120. package/dist/browser/widgets/Widget.js.map +1 -1
  121. package/dist/ssr/actions/ContextMenu.js +9 -0
  122. package/dist/ssr/actions/ContextMenu.js.map +1 -1
  123. package/dist/ssr/actions/Dropdown.js +65 -31
  124. package/dist/ssr/actions/Dropdown.js.map +1 -1
  125. package/dist/ssr/actions/FilterChip.js +67 -16
  126. package/dist/ssr/actions/FilterChip.js.map +1 -1
  127. package/dist/ssr/actions/SelectionToolbar.js +104 -0
  128. package/dist/ssr/actions/SelectionToolbar.js.map +1 -0
  129. package/dist/ssr/actions/filter-messages.js +26 -2
  130. package/dist/ssr/actions/filter-messages.js.map +1 -1
  131. package/dist/ssr/actions/index.js +1 -0
  132. package/dist/ssr/actions/index.js.map +1 -1
  133. package/dist/ssr/actions/menu-rows.js +23 -3
  134. package/dist/ssr/actions/menu-rows.js.map +1 -1
  135. package/dist/ssr/actions/menu-sheet.js +2 -7
  136. package/dist/ssr/actions/menu-sheet.js.map +1 -1
  137. package/dist/ssr/charts.js +6 -0
  138. package/dist/ssr/charts.js.map +1 -0
  139. package/dist/ssr/chat/ChatComposer.js +184 -76
  140. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  141. package/dist/ssr/chat/chat-messages.js +18 -0
  142. package/dist/ssr/chat/chat-messages.js.map +1 -1
  143. package/dist/ssr/chat/composer-document.js +19 -0
  144. package/dist/ssr/chat/composer-document.js.map +1 -1
  145. package/dist/ssr/chat/index.js.map +1 -1
  146. package/dist/ssr/content/Calendar.js +40 -31
  147. package/dist/ssr/content/Calendar.js.map +1 -1
  148. package/dist/ssr/content/CalendarDayView.js +40 -48
  149. package/dist/ssr/content/CalendarDayView.js.map +1 -1
  150. package/dist/ssr/content/Chart.js +8 -0
  151. package/dist/ssr/content/Chart.js.map +1 -1
  152. package/dist/ssr/content/FileGrid.js +132 -5
  153. package/dist/ssr/content/FileGrid.js.map +1 -1
  154. package/dist/ssr/content/FileTypeIcon.js +15 -0
  155. package/dist/ssr/content/FileTypeIcon.js.map +1 -1
  156. package/dist/ssr/content/FileView.js +11 -4
  157. package/dist/ssr/content/FileView.js.map +1 -1
  158. package/dist/ssr/content/MessageRow.js +274 -91
  159. package/dist/ssr/content/MessageRow.js.map +1 -1
  160. package/dist/ssr/content/ReactionBar.js +20 -1
  161. package/dist/ssr/content/ReactionBar.js.map +1 -1
  162. package/dist/ssr/content/calendar-day-scroll.js +4 -3
  163. package/dist/ssr/content/calendar-day-scroll.js.map +1 -1
  164. package/dist/ssr/content/chart-bar.js +476 -0
  165. package/dist/ssr/content/chart-bar.js.map +1 -0
  166. package/dist/ssr/content/file-list-messages.js +52 -0
  167. package/dist/ssr/content/file-list-messages.js.map +1 -0
  168. package/dist/ssr/content/index.js +2 -2
  169. package/dist/ssr/content/index.js.map +1 -1
  170. package/dist/ssr/feedback/Tooltip.js +31 -14
  171. package/dist/ssr/feedback/Tooltip.js.map +1 -1
  172. package/dist/ssr/feedback/dialog-core.js +6 -0
  173. package/dist/ssr/feedback/dialog-core.js.map +1 -1
  174. package/dist/ssr/inputs/ChoiceList.js +95 -0
  175. package/dist/ssr/inputs/ChoiceList.js.map +1 -0
  176. package/dist/ssr/inputs/ChoiceSlider.js +191 -0
  177. package/dist/ssr/inputs/ChoiceSlider.js.map +1 -0
  178. package/dist/ssr/inputs/ChoiceTiles.js +61 -0
  179. package/dist/ssr/inputs/ChoiceTiles.js.map +1 -0
  180. package/dist/ssr/inputs/ColorChoices.js +60 -0
  181. package/dist/ssr/inputs/ColorChoices.js.map +1 -0
  182. package/dist/ssr/inputs/QrScanner.js +89 -10
  183. package/dist/ssr/inputs/QrScanner.js.map +1 -1
  184. package/dist/ssr/inputs/TextInput.js +17 -2
  185. package/dist/ssr/inputs/TextInput.js.map +1 -1
  186. package/dist/ssr/inputs/index.js +4 -0
  187. package/dist/ssr/inputs/index.js.map +1 -1
  188. package/dist/ssr/inputs/qr-messages.js +2 -0
  189. package/dist/ssr/inputs/qr-messages.js.map +1 -1
  190. package/dist/ssr/internal/radio-keys.js +13 -0
  191. package/dist/ssr/internal/radio-keys.js.map +1 -0
  192. package/dist/ssr/intl/shortcut-messages.js +4 -0
  193. package/dist/ssr/intl/shortcut-messages.js.map +1 -1
  194. package/dist/ssr/intl/shortcut.js +4 -0
  195. package/dist/ssr/intl/shortcut.js.map +1 -1
  196. package/dist/ssr/layout/AnchoredPopover.js +142 -0
  197. package/dist/ssr/layout/AnchoredPopover.js.map +1 -0
  198. package/dist/ssr/layout/AppWorkspace.js +287 -141
  199. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  200. package/dist/ssr/layout/DetailPanel.js +181 -108
  201. package/dist/ssr/layout/DetailPanel.js.map +1 -1
  202. package/dist/ssr/layout/MobileShell.js +118 -24
  203. package/dist/ssr/layout/MobileShell.js.map +1 -1
  204. package/dist/ssr/layout/Settings.js +122 -45
  205. package/dist/ssr/layout/Settings.js.map +1 -1
  206. package/dist/ssr/layout/SidebarRailHint.js +79 -0
  207. package/dist/ssr/layout/SidebarRailHint.js.map +1 -0
  208. package/dist/ssr/layout/SidebarSwitcher.js +13 -3
  209. package/dist/ssr/layout/SidebarSwitcher.js.map +1 -1
  210. package/dist/ssr/layout/TabBar.js +11 -3
  211. package/dist/ssr/layout/TabBar.js.map +1 -1
  212. package/dist/ssr/layout/app-workspace-controller.js +11 -4
  213. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  214. package/dist/ssr/layout/detail-panel-messages.js +4 -2
  215. package/dist/ssr/layout/detail-panel-messages.js.map +1 -1
  216. package/dist/ssr/layout/index.js +3 -1
  217. package/dist/ssr/layout/index.js.map +1 -1
  218. package/dist/ssr/layout/scroll-fade.js +32 -5
  219. package/dist/ssr/layout/scroll-fade.js.map +1 -1
  220. package/dist/ssr/layout/settings-messages.js +26 -2
  221. package/dist/ssr/layout/settings-messages.js.map +1 -1
  222. package/dist/ssr/surfaces/Avatar.js +2 -14
  223. package/dist/ssr/surfaces/Avatar.js.map +1 -1
  224. package/dist/ssr/surfaces/DotStrip.js +31 -0
  225. package/dist/ssr/surfaces/DotStrip.js.map +1 -0
  226. package/dist/ssr/surfaces/NoticeCard.js +21 -12
  227. package/dist/ssr/surfaces/NoticeCard.js.map +1 -1
  228. package/dist/ssr/surfaces/ProgressRing.js +3 -0
  229. package/dist/ssr/surfaces/ProgressRing.js.map +1 -1
  230. package/dist/ssr/surfaces/ResourceCard.js +40 -15
  231. package/dist/ssr/surfaces/ResourceCard.js.map +1 -1
  232. package/dist/ssr/surfaces/index.js +2 -0
  233. package/dist/ssr/surfaces/index.js.map +1 -1
  234. package/dist/ssr/surfaces/person-color.js +18 -0
  235. package/dist/ssr/surfaces/person-color.js.map +1 -0
  236. package/dist/ssr/surfaces/resource-messages.js +8 -4
  237. package/dist/ssr/surfaces/resource-messages.js.map +1 -1
  238. package/dist/ssr/widgets/Widget.js +3 -2
  239. package/dist/ssr/widgets/Widget.js.map +1 -1
  240. package/dist/styles.css +1 -1
  241. package/dist/types/actions/ContextMenu.d.ts +5 -0
  242. package/dist/types/actions/Dropdown.d.ts +19 -0
  243. package/dist/types/actions/FilterChip.d.ts +11 -0
  244. package/dist/types/actions/SelectionToolbar.d.ts +38 -0
  245. package/dist/types/actions/filter-messages.d.ts +12 -0
  246. package/dist/types/actions/index.d.ts +2 -0
  247. package/dist/types/actions/menu-rows.d.ts +7 -2
  248. package/dist/types/charts.d.ts +5 -0
  249. package/dist/types/chat/ChatComposer.d.ts +28 -0
  250. package/dist/types/chat/chat-messages.d.ts +9 -0
  251. package/dist/types/chat/composer-document.d.ts +5 -0
  252. package/dist/types/chat/index.d.ts +1 -1
  253. package/dist/types/content/Calendar.d.ts +7 -0
  254. package/dist/types/content/Chart.d.ts +2 -1
  255. package/dist/types/content/FileGrid.d.ts +43 -3
  256. package/dist/types/content/FileTypeIcon.d.ts +10 -0
  257. package/dist/types/content/FileView.d.ts +5 -1
  258. package/dist/types/content/MessageRow.d.ts +66 -2
  259. package/dist/types/content/calendar-day-scroll.d.ts +2 -1
  260. package/dist/types/content/chart-bar.d.ts +60 -0
  261. package/dist/types/content/file-list-messages.d.ts +24 -0
  262. package/dist/types/content/index.d.ts +4 -4
  263. package/dist/types/feedback/Tooltip.d.ts +8 -2
  264. package/dist/types/feedback/dialog-core.d.ts +3 -0
  265. package/dist/types/inputs/ChoiceList.d.ts +28 -0
  266. package/dist/types/inputs/ChoiceSlider.d.ts +36 -0
  267. package/dist/types/inputs/ChoiceTiles.d.ts +25 -0
  268. package/dist/types/inputs/ColorChoices.d.ts +19 -0
  269. package/dist/types/inputs/index.d.ts +8 -0
  270. package/dist/types/inputs/qr-messages.d.ts +1 -0
  271. package/dist/types/internal/radio-keys.d.ts +5 -0
  272. package/dist/types/intl/shortcut-messages.d.ts +2 -0
  273. package/dist/types/layout/AnchoredPopover.d.ts +31 -0
  274. package/dist/types/layout/AppWorkspace.d.ts +12 -0
  275. package/dist/types/layout/DetailPanel.d.ts +17 -0
  276. package/dist/types/layout/MobileShell.d.ts +55 -5
  277. package/dist/types/layout/Settings.d.ts +22 -0
  278. package/dist/types/layout/SidebarRailHint.d.ts +26 -0
  279. package/dist/types/layout/TabBar.d.ts +5 -0
  280. package/dist/types/layout/detail-panel-messages.d.ts +1 -0
  281. package/dist/types/layout/index.d.ts +5 -3
  282. package/dist/types/layout/scroll-fade.d.ts +5 -0
  283. package/dist/types/layout/settings-messages.d.ts +12 -0
  284. package/dist/types/surfaces/DotStrip.d.ts +22 -0
  285. package/dist/types/surfaces/NoticeCard.d.ts +3 -0
  286. package/dist/types/surfaces/ProgressRing.d.ts +2 -1
  287. package/dist/types/surfaces/ResourceCard.d.ts +9 -0
  288. package/dist/types/surfaces/index.d.ts +4 -1
  289. package/dist/types/surfaces/person-color.d.ts +10 -0
  290. package/dist/types/surfaces/resource-messages.d.ts +2 -0
  291. package/dist/types/widgets/Widget.d.ts +3 -2
  292. package/package.json +7 -2
@@ -1,12 +1,23 @@
1
+ import { memo as _$memo } from "solid-js/web";
1
2
  import { ssr as _$ssr } from "solid-js/web";
2
3
  import { createComponent as _$createComponent } from "solid-js/web";
3
4
  import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
4
5
  import { escape as _$escape } from "solid-js/web";
5
- var _tmpl$ = ["<div", ">", "<main class=\"k2b-mobile-shell__main\">", "</main>", "</div>"],
6
- _tmpl$2 = ["<div class=\"k2b-mobile-shell__actions\">", "</div>"],
7
- _tmpl$3 = ["<header class=\"k2b-mobile-shell__header\">", "<h1 class=\"k2b-mobile-shell__title\">", "</h1>", "</header>"],
8
- _tmpl$4 = "<i class=\"ti ti-chevron-left\" aria-hidden=\"true\"></i>";
9
- import { onCleanup, onMount, Show } from "solid-js";
6
+ var _tmpl$ = ["<div", ">", "", "<main class=\"k2b-mobile-shell__main\">", "</main>", "</div>"],
7
+ _tmpl$2 = ["<span class=\"k2b-mobile-shell__title-narrow\">", "</span>"],
8
+ _tmpl$3 = ["<span class=\"k2b-mobile-shell__title-wide\">", "</span>"],
9
+ _tmpl$4 = ["<div class=\"k2b-mobile-shell__actions\">", "</div>"],
10
+ _tmpl$5 = ["<header class=\"k2b-mobile-shell__header\">", "<h1 class=\"k2b-mobile-shell__title\">", "</h1>", "</header>"],
11
+ _tmpl$6 = "<i class=\"ti ti-chevron-left\" aria-hidden=\"true\"></i>",
12
+ _tmpl$7 = ["<a", "><i", " aria-hidden=\"true\"></i><span class=\"k2b-truncate-fade\">", "</span>", "</a>"],
13
+ _tmpl$8 = ["<span class=\"k2b-mobile-shell__sidebar-count\">", "</span>"],
14
+ _tmpl$9 = ["<ul>", "</ul>"],
15
+ _tmpl$0 = ["<li>", "</li>"],
16
+ _tmpl$1 = ["<div class=\"k2b-mobile-shell__sidebar-foot\">", "</div>"],
17
+ _tmpl$10 = ["<nav class=\"k2b-mobile-shell__sidebar\"", "><div class=\"k2b-mobile-shell__sidebar-head\">", "<span class=\"k2b-truncate-fade\">", "</span></div><div class=\"k2b-mobile-shell__sidebar-body\">", "", "</div>", "</nav>"],
18
+ _tmpl$11 = ["<img", " alt width=\"32\" height=\"32\">"],
19
+ _tmpl$12 = ["<section><h2>", "</h2>", "</section>"];
20
+ import { For, onCleanup, onMount, Show } from "solid-js";
10
21
  import { IconButton, IconButtonLink } from "../actions/Button";
11
22
  import ScrollArea from "./ScrollArea";
12
23
  import { installScrollFades } from "./scroll-fade";
@@ -147,24 +158,39 @@ const observeLinkTaps = root => {
147
158
  export function observeMobileShell(root) {
148
159
  const main = root.querySelector(":scope > .k2b-mobile-shell__main");
149
160
  const body = main?.querySelector(":scope > .k2b-mobile-shell__body");
150
- if (!main || !body) return () => {};
161
+ if (!main) return () => {};
151
162
  const stopLinkTaps = observeLinkTaps(root);
152
- const stopScrollFades = installScrollFades(body);
163
+ // Content with its own scroll areas keeps its own fades; the room a toast takes applies to the whole main area.
164
+ const stopScrollFades = body ? installScrollFades(body) : () => {};
153
165
  let frame = 0;
166
+ let owner = null;
167
+ let resize;
168
+ const watchOwner = () => {
169
+ const next = root.querySelector("[data-k2b-shell-bottom]");
170
+ if (next === owner) return;
171
+ if (owner) resize?.unobserve(owner);
172
+ owner = next;
173
+ if (owner) resize?.observe(owner);
174
+ };
154
175
  const measure = () => {
155
176
  frame = 0;
156
- const footer = Math.max(0, root.getBoundingClientRect().bottom - main.getBoundingClientRect().bottom);
177
+ watchOwner();
178
+ const rootBottom = root.getBoundingClientRect().bottom;
179
+ // Content that owns the bottom edge, such as a chat's composer, counts as the footer, so toasts stay above it.
180
+ const owned = owner?.getClientRects().length ? rootBottom - owner.getBoundingClientRect().top : 0;
181
+ const footer = Math.max(0, rootBottom - main.getBoundingClientRect().bottom, owned);
157
182
  document.body.style.setProperty(FOOTER_HEIGHT, `${footer}px`);
158
- root.style.setProperty(TOAST_INSET, `${persistentToastInset(body)}px`);
183
+ root.style.setProperty(TOAST_INSET, `${persistentToastInset(body ?? main)}px`);
159
184
  };
160
185
  const schedule = () => {
161
186
  if (!frame) frame = requestAnimationFrame(measure);
162
187
  };
163
- measure();
164
- // The footer can appear, disappear, or change height; the main area takes whatever it leaves.
165
- const resize = new ResizeObserver(schedule);
188
+ // The footer can appear, disappear, or change height; the main area takes whatever it leaves. Content that owns the
189
+ // bottom edge, such as a composer, grows and shrinks on its own.
190
+ resize = new ResizeObserver(schedule);
166
191
  resize.observe(root);
167
192
  resize.observe(main);
193
+ measure();
168
194
  // Toasts arrive and leave anywhere in the document, and their slots grow and collapse in a transition.
169
195
  const mutations = new MutationObserver(schedule);
170
196
  mutations.observe(document.body, {
@@ -187,15 +213,25 @@ export function observeMobileShell(root) {
187
213
  function MobileShellRoot(props) {
188
214
  let root;
189
215
  onMount(() => onCleanup(observeMobileShell(root)));
190
- return _$ssr(_tmpl$, _$ssrAttribute("class", props.class ? `k2b-mobile-shell ${_$escape(props.class, true)}` : "k2b-mobile-shell", false), _$escape(props.header), _$escape(_$createComponent(ScrollArea, {
191
- "class": "k2b-mobile-shell__body",
192
- get children() {
216
+ return _$ssr(_tmpl$, _$ssrAttribute("class", props.class ? `k2b-mobile-shell ${_$escape(props.class, true)}` : "k2b-mobile-shell", false) + _$ssrAttribute("data-k2b-content-heads", props.scroll === false ? "" : _$escape(undefined, true), false), _$escape(props.sidebar), _$escape(props.header), _$escape(_$createComponent(Show, {
217
+ get when() {
218
+ return props.scroll !== false;
219
+ },
220
+ get fallback() {
193
221
  return props.children;
222
+ },
223
+ get children() {
224
+ return _$createComponent(ScrollArea, {
225
+ "class": "k2b-mobile-shell__body",
226
+ get children() {
227
+ return props.children;
228
+ }
229
+ });
194
230
  }
195
231
  })), _$escape(props.footer));
196
232
  }
197
233
  function MobileShellHeader(props) {
198
- return _$ssr(_tmpl$3, _$escape(_$createComponent(Show, {
234
+ return _$ssr(_tmpl$5, _$escape(_$createComponent(Show, {
199
235
  get when() {
200
236
  return props.back;
201
237
  },
@@ -206,18 +242,24 @@ function MobileShellHeader(props) {
206
242
  get fallback() {
207
243
  return _$createComponent(IconButton, {
208
244
  "class": "k2b-mobile-shell__back",
245
+ get ["data-with-sidebar"]() {
246
+ return back().withSidebar === false ? "false" : undefined;
247
+ },
209
248
  get label() {
210
249
  return back().label;
211
250
  },
212
251
  tooltip: false,
213
252
  onClick: () => back().onClick?.(),
214
253
  get children() {
215
- return _$ssr(_tmpl$4);
254
+ return _$ssr(_tmpl$6);
216
255
  }
217
256
  });
218
257
  },
219
258
  children: href => _$createComponent(IconButtonLink, {
220
259
  "class": "k2b-mobile-shell__back",
260
+ get ["data-with-sidebar"]() {
261
+ return back().withSidebar === false ? "false" : undefined;
262
+ },
221
263
  get href() {
222
264
  return href();
223
265
  },
@@ -227,27 +269,79 @@ function MobileShellHeader(props) {
227
269
  tooltip: false,
228
270
  onClick: () => back().onClick?.(),
229
271
  get children() {
230
- return _$ssr(_tmpl$4);
272
+ return _$ssr(_tmpl$6);
231
273
  }
232
274
  })
233
275
  })
234
- })), _$escape(props.title), _$escape(_$createComponent(Show, {
276
+ })), _$escape(_$createComponent(Show, {
277
+ get when() {
278
+ return _$memo(() => props.sidebarTitle !== undefined)() && props.sidebarTitle !== props.title;
279
+ },
280
+ get fallback() {
281
+ return props.title;
282
+ },
283
+ get children() {
284
+ return [_$ssr(_tmpl$2, _$escape(props.title)), _$ssr(_tmpl$3, _$escape(props.sidebarTitle))];
285
+ }
286
+ })), _$escape(_$createComponent(Show, {
235
287
  get when() {
236
288
  return props.actions;
237
289
  },
238
290
  get children() {
239
- return _$ssr(_tmpl$2, _$escape(props.actions));
291
+ return _$ssr(_tmpl$4, _$escape(props.actions));
292
+ }
293
+ })));
294
+ }
295
+ function SidebarLink(props) {
296
+ return _$ssr(_tmpl$7, _$ssrAttribute("href", _$escape(props.item.href, true), false) + _$ssrAttribute("aria-current", props.item.current ? "page" : _$escape(undefined, true), false) + _$ssrAttribute("data-tab", _$escape(props.item.id, true), false) + _$ssrAttribute("data-k2b-title", _$escape(props.item.title ?? props.item.label, true), false), _$ssrAttribute("class", _$escape(props.item.icon, true), false), _$escape(props.item.label), _$escape(_$createComponent(Show, {
297
+ get when() {
298
+ return props.item.badge ?? props.item.count;
299
+ },
300
+ children: mark => _$ssr(_tmpl$8, _$escape(mark()))
301
+ })));
302
+ }
303
+
304
+ /**
305
+ * The navigation of a touch app on a wide screen, in `MobileShell`'s `sidebar`: the app's name, then its destinations
306
+ * as 44 px rows with icon and label, the open one in the action colour. A press shows the item's page frame at once,
307
+ * as on a tab, and the row of a loading page pulses.
308
+ */
309
+ function MobileShellSidebar(props) {
310
+ const list = items => _$ssr(_tmpl$9, _$escape(_$createComponent(For, {
311
+ each: items,
312
+ children: item => _$ssr(_tmpl$0, _$escape(_$createComponent(SidebarLink, {
313
+ item: item
314
+ })))
315
+ })));
316
+ return _$ssr(_tmpl$10, _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$escape(_$createComponent(Show, {
317
+ get when() {
318
+ return props.logo;
319
+ },
320
+ children: logo => _$ssr(_tmpl$11, _$ssrAttribute("src", _$escape(logo(), true), false))
321
+ })), _$escape(props.title), _$escape(list(props.items)), _$escape(_$createComponent(For, {
322
+ get each() {
323
+ return (props.sections ?? []).filter(section => section.items.length > 0);
324
+ },
325
+ children: section => _$ssr(_tmpl$12, _$escape(section.title), _$escape(list(section.items)))
326
+ })), _$escape(_$createComponent(Show, {
327
+ get when() {
328
+ return props.footer?.length;
329
+ },
330
+ get children() {
331
+ return _$ssr(_tmpl$1, _$escape(list(props.footer ?? [])));
240
332
  }
241
333
  })));
242
334
  }
243
335
 
244
336
  /**
245
- * The full-screen frame of a phone app: a header, one scroll area, and an optional footer such as a `TabBar`. While
246
- * it is mounted, the document itself never scrolls, pinch zoom and rubber-banding are off, and the toast rail sits
247
- * above the footer. Mount one per page, as the page's layout.
337
+ * The full-screen frame of a phone app: a header, one scroll area, and an optional footer such as a `TabBar`. With a
338
+ * `sidebar`, a window of 48rem and wider shows the sidebar at the start edge instead of the footer, and the toast rail
339
+ * stays in the area beside it. While it is mounted, the document itself never scrolls, pinch zoom and rubber-banding
340
+ * are off, and the toast rail sits above the footer. Mount one per page, as the page's layout.
248
341
  */
249
342
  export const MobileShell = Object.assign(MobileShellRoot, {
250
- Header: MobileShellHeader
343
+ Header: MobileShellHeader,
344
+ Sidebar: MobileShellSidebar
251
345
  });
252
346
  export default MobileShell;
253
347
  //# sourceMappingURL=MobileShell.js.map
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["layout/MobileShell.tsx"],"sourcesContent":["import { type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { IconButton, IconButtonLink } from \"../actions/Button\";\nimport ScrollArea from \"./ScrollArea\";\nimport { installScrollFades } from \"./scroll-fade\";\n\nexport type MobileShellProps = {\n /** Usually `MobileShell.Header`; it sits above the scroll area and pads the top safe area. */\n header: JSX.Element;\n /** Usually a `TabBar`; it sits below the scroll area and pads the bottom safe area itself. */\n footer?: JSX.Element;\n /** The page content, inside the shell's only scroll area. */\n children: JSX.Element;\n class?: string;\n};\n\nexport type MobileShellHeaderProps = {\n title: string;\n /** A Back control before the title: a link with `href`, otherwise a button. */\n back?: { href?: string; onClick?: () => void; label: string };\n /** Controls after the title, such as a menu. */\n actions?: JSX.Element;\n};\n\n/** Read by the toast rail, which is portalled outside the shell, so it lives on the body. */\nconst FOOTER_HEIGHT = \"--k2b-mobile-shell-footer-height\";\n/** How much of the scroll area a persistent toast covers; local to the shell. */\nconst TOAST_INSET = \"--k2b-mobile-shell-toast-inset\";\n\n/**\n * The visible part of the toast rail when it holds a toast without a timer (`duration: 0`, running progress, or a\n * custom slot), so the last row of the scroll area can still scroll above it. A rail that sits elsewhere, such as\n * above a dialog, covers nothing.\n */\nconst persistentToastInset = (body: HTMLElement): number => {\n if (document.querySelector(\"dialog:modal\")) return 0;\n const cards = [...document.querySelectorAll<HTMLElement>(\"[data-k2b-toast-container] [data-k2b-toast]:not([data-closing])\")];\n if (!cards.some((card) => card.dataset.persistent === \"true\")) return 0;\n const area = body.getBoundingClientRect();\n const top = Math.min(...cards.map((card) => card.getBoundingClientRect().top));\n return Math.max(0, Math.min(area.height, area.bottom - top));\n};\n\n/** Marks the link whose page is loading; the tab bar shows it as selected. */\nconst PENDING = \"data-k2b-pending\";\n/** On a link: the title of the page it opens. Such a link switches the shell to that page's frame at the first touch. */\nconst PAGE_TITLE = \"data-k2b-title\";\n/** On the root while the shell shows the frame of the page that is loading. */\nconst SWITCHING = \"data-k2b-switching\";\n/** On the header title while the shell switches: the loading page's title, which the title shows instead. */\nconst NEXT_TITLE = \"data-k2b-next-title\";\n\n/** The link a click or press follows as a page load of this origin, or null when it does something else. */\nconst pageLink = (root: HTMLElement, event: MouseEvent): HTMLAnchorElement | null => {\n if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return null;\n const link = event.target instanceof Element ? event.target.closest<HTMLAnchorElement>(\"a[href]\") : null;\n if (!link || !root.contains(link) || link.hasAttribute(\"download\") || (link.target && link.target !== \"_self\")) return null;\n const url = new URL(link.href);\n if (url.origin !== location.origin) return null;\n // A jump inside the page loads nothing.\n if (url.hash && url.pathname === location.pathname && url.search === location.search) return null;\n return link;\n};\n\n/**\n * Taps on the shell's links. iOS shows `:active` only while the page listens to touches, so a tap gets its pressed\n * state at once. A link's page keeps loading while the old page stays visible; a second tap on it would cancel that\n * load and start it over, so it is ignored. A link that names its page's title, such as a tab, shows that page's\n * frame at the first touch instead of at the end of the tap: its title and an empty content area, until the page\n * arrives. Returns the cleanup.\n */\nconst observeLinkTaps = (root: HTMLElement): (() => void) => {\n /** The link marked as the next page. */\n let pending: string | undefined;\n /** Whether its page is loading; a pressed tab waits for its click. */\n let loading = false;\n const clear = () => {\n pending = undefined;\n loading = false;\n for (const link of root.querySelectorAll(`[${PENDING}]`)) link.removeAttribute(PENDING);\n root.removeAttribute(SWITCHING);\n root.querySelector(\".k2b-mobile-shell__title\")?.removeAttribute(NEXT_TITLE);\n };\n const mark = (link: HTMLAnchorElement) => {\n clear();\n pending = link.href;\n link.setAttribute(PENDING, \"\");\n const title = link.getAttribute(PAGE_TITLE);\n const heading = root.querySelector(\".k2b-mobile-shell__title\");\n if (title === null || !heading) return;\n heading.setAttribute(NEXT_TITLE, title);\n root.setAttribute(SWITCHING, \"\");\n };\n const touch = () => {};\n // A press on a tab shows its page's frame while the finger is still down. The page itself loads with the click\n // that ends the tap, as for any link: only the end of a touch counts as the person's action, and a load that\n // started before it could let the browser's Back skip this page.\n const press = (event: PointerEvent) => {\n if (!event.isPrimary) return;\n const link = pageLink(root, event);\n if (!link?.hasAttribute(PAGE_TITLE) || (link.href === pending && loading)) return;\n mark(link);\n };\n // A press that the browser takes over, such as one that turns into a scroll, ends without a click and takes its\n // frame back.\n const cancel = (event: PointerEvent) => {\n if (event.isPrimary && !loading) clear();\n };\n // On the window, so it runs after every handler of the click, including delegated ones that prevent it.\n const click = (event: MouseEvent) => {\n const link = pageLink(root, event);\n if (link && link.href === pending && loading) {\n event.preventDefault();\n return;\n }\n // Any other tap ends the wait, also for a link that answered with a download instead of a page.\n clear();\n if (!link) return;\n mark(link);\n loading = true;\n };\n // iOS sends no click for a tap on content without an action, so the end of a tap anywhere else ends the wait as\n // well. A touch that scrolls ends without a pointerup and keeps a load's wait.\n const release = (event: PointerEvent) => {\n if (pending === undefined) return;\n const link = event.target instanceof Element ? event.target.closest<HTMLAnchorElement>(\"a[href]\") : null;\n if (link?.href !== pending) clear();\n };\n // A page restored from the back/forward cache shows the state it was left in.\n const show = (event: PageTransitionEvent) => {\n if (event.persisted) clear();\n };\n document.addEventListener(\"touchstart\", touch, { passive: true });\n window.addEventListener(\"pointerdown\", press);\n window.addEventListener(\"pointercancel\", cancel);\n window.addEventListener(\"pointerup\", release);\n window.addEventListener(\"click\", click);\n window.addEventListener(\"pageshow\", show);\n return () => {\n document.removeEventListener(\"touchstart\", touch);\n window.removeEventListener(\"pointerdown\", press);\n window.removeEventListener(\"pointercancel\", cancel);\n window.removeEventListener(\"pointerup\", release);\n window.removeEventListener(\"click\", click);\n window.removeEventListener(\"pageshow\", show);\n clear();\n };\n};\n\n/**\n * Keeps a mounted shell working: the footer height on the body, which the portalled toast rail reads, the room a\n * persistent toast takes from the scroll area, the scroll area's edge fade, and immediate feedback for link taps.\n * The fade is the only cue at the footer's edge, which has no rule. `MobileShell` calls it itself;\n * a page that renders the shell on the server without hydrating it calls it once in the browser. Returns the\n * cleanup.\n */\nexport function observeMobileShell(root: HTMLElement): () => void {\n const main = root.querySelector<HTMLElement>(\":scope > .k2b-mobile-shell__main\");\n const body = main?.querySelector<HTMLElement>(\":scope > .k2b-mobile-shell__body\");\n if (!main || !body) return () => {};\n const stopLinkTaps = observeLinkTaps(root);\n const stopScrollFades = installScrollFades(body);\n let frame = 0;\n const measure = () => {\n frame = 0;\n const footer = Math.max(0, root.getBoundingClientRect().bottom - main.getBoundingClientRect().bottom);\n document.body.style.setProperty(FOOTER_HEIGHT, `${footer}px`);\n root.style.setProperty(TOAST_INSET, `${persistentToastInset(body)}px`);\n };\n const schedule = () => {\n if (!frame) frame = requestAnimationFrame(measure);\n };\n measure();\n // The footer can appear, disappear, or change height; the main area takes whatever it leaves.\n const resize = new ResizeObserver(schedule);\n resize.observe(root);\n resize.observe(main);\n // Toasts arrive and leave anywhere in the document, and their slots grow and collapse in a transition.\n const mutations = new MutationObserver(schedule);\n mutations.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"data-state\", \"data-closing\", \"data-persistent\", \"open\"],\n });\n document.addEventListener(\"transitionend\", schedule, true);\n return () => {\n if (frame) cancelAnimationFrame(frame);\n resize.disconnect();\n mutations.disconnect();\n document.removeEventListener(\"transitionend\", schedule, true);\n document.body.style.removeProperty(FOOTER_HEIGHT);\n stopLinkTaps();\n stopScrollFades();\n };\n}\n\nfunction MobileShellRoot(props: MobileShellProps): JSX.Element {\n let root!: HTMLDivElement;\n\n onMount(() => onCleanup(observeMobileShell(root)));\n\n return (\n <div ref={root} class={props.class ? `k2b-mobile-shell ${props.class}` : \"k2b-mobile-shell\"}>\n {props.header}\n <main class=\"k2b-mobile-shell__main\">\n <ScrollArea class=\"k2b-mobile-shell__body\">{props.children}</ScrollArea>\n </main>\n {props.footer}\n </div>\n );\n}\n\nfunction MobileShellHeader(props: MobileShellHeaderProps): JSX.Element {\n return (\n <header class=\"k2b-mobile-shell__header\">\n <Show when={props.back}>\n {(back) => (\n <Show\n when={back().href}\n fallback={\n <IconButton class=\"k2b-mobile-shell__back\" label={back().label} tooltip={false} onClick={() => back().onClick?.()}>\n <i class=\"ti ti-chevron-left\" aria-hidden=\"true\" />\n </IconButton>\n }\n >\n {(href) => (\n <IconButtonLink\n class=\"k2b-mobile-shell__back\"\n href={href()}\n label={back().label}\n tooltip={false}\n onClick={() => back().onClick?.()}\n >\n <i class=\"ti ti-chevron-left\" aria-hidden=\"true\" />\n </IconButtonLink>\n )}\n </Show>\n )}\n </Show>\n <h1 class=\"k2b-mobile-shell__title\">{props.title}</h1>\n <Show when={props.actions}>\n <div class=\"k2b-mobile-shell__actions\">{props.actions}</div>\n </Show>\n </header>\n );\n}\n\n/**\n * The full-screen frame of a phone app: a header, one scroll area, and an optional footer such as a `TabBar`. While\n * it is mounted, the document itself never scrolls, pinch zoom and rubber-banding are off, and the toast rail sits\n * above the footer. Mount one per page, as the page's layout.\n */\nexport const MobileShell = Object.assign(MobileShellRoot, { Header: MobileShellHeader });\n\nexport default MobileShell;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["layout/MobileShell.tsx"],"sourcesContent":["import { For, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { IconButton, IconButtonLink } from \"../actions/Button\";\nimport ScrollArea from \"./ScrollArea\";\nimport { installScrollFades } from \"./scroll-fade\";\nimport type { TabBarItem } from \"./TabBar\";\n\nexport type MobileShellProps = {\n /** Usually `MobileShell.Header`; it sits above the scroll area and pads the top safe area. */\n header?: JSX.Element;\n /** Usually a `TabBar`; it sits below the scroll area and pads the bottom safe area itself. */\n footer?: JSX.Element;\n /**\n * Usually `MobileShell.Sidebar` with the footer's destinations and more. From 48rem of window width it stands at\n * the start edge and the footer goes; below, it is not shown. The server renders both, so CSS alone decides.\n */\n sidebar?: JSX.Element;\n /** The page content, inside the shell's only scroll area. */\n children: JSX.Element;\n /**\n * `false` for content that brings its own heads and scroll areas, such as `AppWorkspace.Main` levels: it fills the\n * main area without the shell's scroll area, and the header shows only as the frame of the next page while a tapped\n * tab's page loads.\n */\n scroll?: boolean;\n class?: string;\n};\n\nexport type MobileShellHeaderProps = {\n title: string;\n /** The title while the shell shows its sidebar, which already names the app; `title` by default. */\n sidebarTitle?: string;\n /**\n * A Back control before the title: a link with `href`, otherwise a button. `withSidebar: false` hides it while the\n * shell shows its sidebar, for a way back to a destination the sidebar lists.\n */\n back?: { href?: string; onClick?: () => void; label: string; withSidebar?: boolean };\n /** Controls after the title, such as a menu. */\n actions?: JSX.Element;\n};\n\n/** Read by the toast rail, which is portalled outside the shell, so it lives on the body. */\nconst FOOTER_HEIGHT = \"--k2b-mobile-shell-footer-height\";\n/** How much of the scroll area a persistent toast covers; local to the shell. */\nconst TOAST_INSET = \"--k2b-mobile-shell-toast-inset\";\n\n/**\n * The visible part of the toast rail when it holds a toast without a timer (`duration: 0`, running progress, or a\n * custom slot), so the last row of the scroll area can still scroll above it. A rail that sits elsewhere, such as\n * above a dialog, covers nothing.\n */\nconst persistentToastInset = (body: HTMLElement): number => {\n if (document.querySelector(\"dialog:modal\")) return 0;\n const cards = [...document.querySelectorAll<HTMLElement>(\"[data-k2b-toast-container] [data-k2b-toast]:not([data-closing])\")];\n if (!cards.some((card) => card.dataset.persistent === \"true\")) return 0;\n const area = body.getBoundingClientRect();\n const top = Math.min(...cards.map((card) => card.getBoundingClientRect().top));\n return Math.max(0, Math.min(area.height, area.bottom - top));\n};\n\n/** Marks the link whose page is loading; the tab bar shows it as selected. */\nconst PENDING = \"data-k2b-pending\";\n/** On a link: the title of the page it opens. Such a link switches the shell to that page's frame at the first touch. */\nconst PAGE_TITLE = \"data-k2b-title\";\n/** On the root while the shell shows the frame of the page that is loading. */\nconst SWITCHING = \"data-k2b-switching\";\n/** On the header title while the shell switches: the loading page's title, which the title shows instead. */\nconst NEXT_TITLE = \"data-k2b-next-title\";\n\n/** The link a click or press follows as a page load of this origin, or null when it does something else. */\nconst pageLink = (root: HTMLElement, event: MouseEvent): HTMLAnchorElement | null => {\n if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return null;\n const link = event.target instanceof Element ? event.target.closest<HTMLAnchorElement>(\"a[href]\") : null;\n if (!link || !root.contains(link) || link.hasAttribute(\"download\") || (link.target && link.target !== \"_self\")) return null;\n const url = new URL(link.href);\n if (url.origin !== location.origin) return null;\n // A jump inside the page loads nothing.\n if (url.hash && url.pathname === location.pathname && url.search === location.search) return null;\n return link;\n};\n\n/**\n * Taps on the shell's links. iOS shows `:active` only while the page listens to touches, so a tap gets its pressed\n * state at once. A link's page keeps loading while the old page stays visible; a second tap on it would cancel that\n * load and start it over, so it is ignored. A link that names its page's title, such as a tab, shows that page's\n * frame at the first touch instead of at the end of the tap: its title and an empty content area, until the page\n * arrives. Returns the cleanup.\n */\nconst observeLinkTaps = (root: HTMLElement): (() => void) => {\n /** The link marked as the next page. */\n let pending: string | undefined;\n /** Whether its page is loading; a pressed tab waits for its click. */\n let loading = false;\n const clear = () => {\n pending = undefined;\n loading = false;\n for (const link of root.querySelectorAll(`[${PENDING}]`)) link.removeAttribute(PENDING);\n root.removeAttribute(SWITCHING);\n root.querySelector(\".k2b-mobile-shell__title\")?.removeAttribute(NEXT_TITLE);\n };\n const mark = (link: HTMLAnchorElement) => {\n clear();\n pending = link.href;\n link.setAttribute(PENDING, \"\");\n const title = link.getAttribute(PAGE_TITLE);\n const heading = root.querySelector(\".k2b-mobile-shell__title\");\n if (title === null || !heading) return;\n heading.setAttribute(NEXT_TITLE, title);\n root.setAttribute(SWITCHING, \"\");\n };\n const touch = () => {};\n // A press on a tab shows its page's frame while the finger is still down. The page itself loads with the click\n // that ends the tap, as for any link: only the end of a touch counts as the person's action, and a load that\n // started before it could let the browser's Back skip this page.\n const press = (event: PointerEvent) => {\n if (!event.isPrimary) return;\n const link = pageLink(root, event);\n if (!link?.hasAttribute(PAGE_TITLE) || (link.href === pending && loading)) return;\n mark(link);\n };\n // A press that the browser takes over, such as one that turns into a scroll, ends without a click and takes its\n // frame back.\n const cancel = (event: PointerEvent) => {\n if (event.isPrimary && !loading) clear();\n };\n // On the window, so it runs after every handler of the click, including delegated ones that prevent it.\n const click = (event: MouseEvent) => {\n const link = pageLink(root, event);\n if (link && link.href === pending && loading) {\n event.preventDefault();\n return;\n }\n // Any other tap ends the wait, also for a link that answered with a download instead of a page.\n clear();\n if (!link) return;\n mark(link);\n loading = true;\n };\n // iOS sends no click for a tap on content without an action, so the end of a tap anywhere else ends the wait as\n // well. A touch that scrolls ends without a pointerup and keeps a load's wait.\n const release = (event: PointerEvent) => {\n if (pending === undefined) return;\n const link = event.target instanceof Element ? event.target.closest<HTMLAnchorElement>(\"a[href]\") : null;\n if (link?.href !== pending) clear();\n };\n // A page restored from the back/forward cache shows the state it was left in.\n const show = (event: PageTransitionEvent) => {\n if (event.persisted) clear();\n };\n document.addEventListener(\"touchstart\", touch, { passive: true });\n window.addEventListener(\"pointerdown\", press);\n window.addEventListener(\"pointercancel\", cancel);\n window.addEventListener(\"pointerup\", release);\n window.addEventListener(\"click\", click);\n window.addEventListener(\"pageshow\", show);\n return () => {\n document.removeEventListener(\"touchstart\", touch);\n window.removeEventListener(\"pointerdown\", press);\n window.removeEventListener(\"pointercancel\", cancel);\n window.removeEventListener(\"pointerup\", release);\n window.removeEventListener(\"click\", click);\n window.removeEventListener(\"pageshow\", show);\n clear();\n };\n};\n\n/**\n * Keeps a mounted shell working: the footer height on the body, which the portalled toast rail reads, the room a\n * persistent toast takes from the scroll area, the scroll area's edge fade, and immediate feedback for link taps.\n * The fade is the only cue at the footer's edge, which has no rule. `MobileShell` calls it itself;\n * a page that renders the shell on the server without hydrating it calls it once in the browser. Returns the\n * cleanup.\n */\nexport function observeMobileShell(root: HTMLElement): () => void {\n const main = root.querySelector<HTMLElement>(\":scope > .k2b-mobile-shell__main\");\n const body = main?.querySelector<HTMLElement>(\":scope > .k2b-mobile-shell__body\");\n if (!main) return () => {};\n const stopLinkTaps = observeLinkTaps(root);\n // Content with its own scroll areas keeps its own fades; the room a toast takes applies to the whole main area.\n const stopScrollFades = body ? installScrollFades(body) : () => {};\n let frame = 0;\n let owner: HTMLElement | null = null;\n let resize: ResizeObserver | undefined;\n const watchOwner = () => {\n const next = root.querySelector<HTMLElement>(\"[data-k2b-shell-bottom]\");\n if (next === owner) return;\n if (owner) resize?.unobserve(owner);\n owner = next;\n if (owner) resize?.observe(owner);\n };\n const measure = () => {\n frame = 0;\n watchOwner();\n const rootBottom = root.getBoundingClientRect().bottom;\n // Content that owns the bottom edge, such as a chat's composer, counts as the footer, so toasts stay above it.\n const owned = owner?.getClientRects().length ? rootBottom - owner.getBoundingClientRect().top : 0;\n const footer = Math.max(0, rootBottom - main.getBoundingClientRect().bottom, owned);\n document.body.style.setProperty(FOOTER_HEIGHT, `${footer}px`);\n root.style.setProperty(TOAST_INSET, `${persistentToastInset(body ?? main)}px`);\n };\n const schedule = () => {\n if (!frame) frame = requestAnimationFrame(measure);\n };\n // The footer can appear, disappear, or change height; the main area takes whatever it leaves. Content that owns the\n // bottom edge, such as a composer, grows and shrinks on its own.\n resize = new ResizeObserver(schedule);\n resize.observe(root);\n resize.observe(main);\n measure();\n // Toasts arrive and leave anywhere in the document, and their slots grow and collapse in a transition.\n const mutations = new MutationObserver(schedule);\n mutations.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"data-state\", \"data-closing\", \"data-persistent\", \"open\"],\n });\n document.addEventListener(\"transitionend\", schedule, true);\n return () => {\n if (frame) cancelAnimationFrame(frame);\n resize.disconnect();\n mutations.disconnect();\n document.removeEventListener(\"transitionend\", schedule, true);\n document.body.style.removeProperty(FOOTER_HEIGHT);\n stopLinkTaps();\n stopScrollFades();\n };\n}\n\nfunction MobileShellRoot(props: MobileShellProps): JSX.Element {\n let root!: HTMLDivElement;\n\n onMount(() => onCleanup(observeMobileShell(root)));\n\n return (\n <div\n ref={root}\n class={props.class ? `k2b-mobile-shell ${props.class}` : \"k2b-mobile-shell\"}\n data-k2b-content-heads={props.scroll === false ? \"\" : undefined}\n >\n {props.sidebar}\n {props.header}\n <main class=\"k2b-mobile-shell__main\">\n <Show when={props.scroll !== false} fallback={props.children}>\n <ScrollArea class=\"k2b-mobile-shell__body\">{props.children}</ScrollArea>\n </Show>\n </main>\n {props.footer}\n </div>\n );\n}\n\nfunction MobileShellHeader(props: MobileShellHeaderProps): JSX.Element {\n return (\n <header class=\"k2b-mobile-shell__header\">\n <Show when={props.back}>\n {(back) => (\n <Show\n when={back().href}\n fallback={\n <IconButton\n class=\"k2b-mobile-shell__back\"\n data-with-sidebar={back().withSidebar === false ? \"false\" : undefined}\n label={back().label}\n tooltip={false}\n onClick={() => back().onClick?.()}\n >\n <i class=\"ti ti-chevron-left\" aria-hidden=\"true\" />\n </IconButton>\n }\n >\n {(href) => (\n <IconButtonLink\n class=\"k2b-mobile-shell__back\"\n data-with-sidebar={back().withSidebar === false ? \"false\" : undefined}\n href={href()}\n label={back().label}\n tooltip={false}\n onClick={() => back().onClick?.()}\n >\n <i class=\"ti ti-chevron-left\" aria-hidden=\"true\" />\n </IconButtonLink>\n )}\n </Show>\n )}\n </Show>\n <h1 class=\"k2b-mobile-shell__title\">\n <Show when={props.sidebarTitle !== undefined && props.sidebarTitle !== props.title} fallback={props.title}>\n <span class=\"k2b-mobile-shell__title-narrow\">{props.title}</span>\n <span class=\"k2b-mobile-shell__title-wide\">{props.sidebarTitle}</span>\n </Show>\n </h1>\n <Show when={props.actions}>\n <div class=\"k2b-mobile-shell__actions\">{props.actions}</div>\n </Show>\n </header>\n );\n}\n\nexport type MobileShellSidebarItem = TabBarItem & {\n /**\n * A quiet count after the label, such as open tasks: a number (0 shows nothing) or an element that renders one. The\n * item's `badge`, which calls for attention, takes its place.\n */\n count?: number | JSX.Element;\n};\n\nexport type MobileShellSidebarProps = {\n /** Accessible name of the navigation landmark; usually the tab bar's. */\n label: string;\n /** The app's name in the sidebar's head. */\n title: string;\n /** Address of the app's logo before the name; decorative, the name says it. */\n logo?: string;\n /** Destinations at the top, such as Start. */\n items: readonly MobileShellSidebarItem[];\n /** Named groups below them, such as the apps; a group without items is left out. */\n sections?: readonly { title: string; items: readonly MobileShellSidebarItem[] }[];\n /** Destinations at the bottom edge, such as Settings. */\n footer?: readonly MobileShellSidebarItem[];\n};\n\nfunction SidebarLink(props: { item: MobileShellSidebarItem }): JSX.Element {\n return (\n <a\n href={props.item.href}\n aria-current={props.item.current ? \"page\" : undefined}\n data-tab={props.item.id}\n data-k2b-title={props.item.title ?? props.item.label}\n >\n <i class={props.item.icon} aria-hidden=\"true\" />\n <span class=\"k2b-truncate-fade\">{props.item.label}</span>\n <Show when={props.item.badge ?? props.item.count}>{(mark) => <span class=\"k2b-mobile-shell__sidebar-count\">{mark()}</span>}</Show>\n </a>\n );\n}\n\n/**\n * The navigation of a touch app on a wide screen, in `MobileShell`'s `sidebar`: the app's name, then its destinations\n * as 44 px rows with icon and label, the open one in the action colour. A press shows the item's page frame at once,\n * as on a tab, and the row of a loading page pulses.\n */\nfunction MobileShellSidebar(props: MobileShellSidebarProps): JSX.Element {\n const list = (items: readonly MobileShellSidebarItem[]) => (\n <ul>\n <For each={items}>\n {(item) => (\n <li>\n <SidebarLink item={item} />\n </li>\n )}\n </For>\n </ul>\n );\n return (\n <nav class=\"k2b-mobile-shell__sidebar\" aria-label={props.label}>\n <div class=\"k2b-mobile-shell__sidebar-head\">\n <Show when={props.logo}>{(logo) => <img src={logo()} alt=\"\" width=\"32\" height=\"32\" />}</Show>\n <span class=\"k2b-truncate-fade\">{props.title}</span>\n </div>\n <div class=\"k2b-mobile-shell__sidebar-body\">\n {list(props.items)}\n <For each={(props.sections ?? []).filter((section) => section.items.length > 0)}>\n {(section) => (\n <section>\n <h2>{section.title}</h2>\n {list(section.items)}\n </section>\n )}\n </For>\n </div>\n <Show when={props.footer?.length}>\n <div class=\"k2b-mobile-shell__sidebar-foot\">{list(props.footer ?? [])}</div>\n </Show>\n </nav>\n );\n}\n\n/**\n * The full-screen frame of a phone app: a header, one scroll area, and an optional footer such as a `TabBar`. With a\n * `sidebar`, a window of 48rem and wider shows the sidebar at the start edge instead of the footer, and the toast rail\n * stays in the area beside it. While it is mounted, the document itself never scrolls, pinch zoom and rubber-banding\n * are off, and the toast rail sits above the footer. Mount one per page, as the page's layout.\n */\nexport const MobileShell = Object.assign(MobileShellRoot, { Header: MobileShellHeader, Sidebar: MobileShellSidebar });\n\nexport default MobileShell;\n"],"mappings":"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","ignoreList":[]}
@@ -1,58 +1,67 @@
1
1
  import { mergeProps as _$mergeProps } from "solid-js/web";
2
2
  import { memo as _$memo } from "solid-js/web";
3
- import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
4
3
  import { ssr as _$ssr } from "solid-js/web";
5
4
  import { createComponent as _$createComponent } from "solid-js/web";
5
+ import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
6
6
  import { ssrStyle as _$ssrStyle } from "solid-js/web";
7
7
  import { escape as _$escape } from "solid-js/web";
8
8
  var _tmpl$ = ["<p>", "</p>"],
9
9
  _tmpl$2 = ["<div class=\"k2b-settings-page__actions\">", "</div>"],
10
- _tmpl$3 = ["<footer class=\"k2b-settings-page__footer\">", "</footer>"],
11
- _tmpl$4 = ["<section class=\"", "\" style=\"", "\"><header class=\"k2b-settings-page__header\">", "<div class=\"k2b-settings-page__heading\"><h1>", "</h1>", "</div>", "</header><div class=\"k2b-settings-page__body\"", ">", "</div>", "</section>"],
12
- _tmpl$5 = ["<i", " aria-hidden=\"true\"></i>"],
13
- _tmpl$6 = ["<div class=\"k2b-settings-section__actions\">", "</div>"],
14
- _tmpl$7 = ["<section class=\"", "\"", "><header class=\"k2b-settings-section__header\">", "<div class=\"k2b-settings-section__heading\"><h2", ">", "</h2>", "</div>", "</header><div class=\"k2b-settings-section__body\">", "</div></section>"],
15
- _tmpl$8 = ["<section class=\"", "\"", "><header class=\"k2b-settings-group__header\"><div class=\"k2b-settings-group__heading\"><h3", ">", "</h3>", "</div>", "</header><div class=\"k2b-settings-group__body\">", "</div></section>"],
16
- _tmpl$9 = ["<div class=\"k2b-settings-group__action\">", "</div>"],
17
- _tmpl$0 = ["<span>", "</span>"],
18
- _tmpl$1 = ["<div class=\"", "\"><div class=\"k2b-settings-field__copy\"><div class=\"k2b-settings-field__heading\"><strong>", "</strong>", "</div><p", ">", "</p></div>", "", "</div>"],
19
- _tmpl$10 = ["<p", " class=\"k2b-settings-field__error\" role=\"alert\"><i class=\"ti ti-alert-circle\" aria-hidden=\"true\"></i> ", "</p>"],
20
- _tmpl$11 = "<i class=\"ti ti-device-floppy\" aria-hidden=\"true\"></i>",
21
- _tmpl$12 = ["<div class=\"k2b-settings-actions\">", "", "</div>"],
22
- _tmpl$13 = ["<div class=\"", "\"><p role=\"status\" aria-live=\"polite\"><strong>", "</strong> ", "</p>", "</div>"],
23
- _tmpl$14 = ["<strong>", "</strong>"],
24
- _tmpl$15 = ["<p class=\"k2b-settings-panel-footer__status\" role=\"status\" aria-live=\"polite\">", "</p>"];
10
+ _tmpl$3 = ["<header class=\"k2b-settings-page__header\">", "<div class=\"k2b-settings-page__heading\"><h1>", "</h1>", "</div>", "</header>"],
11
+ _tmpl$4 = ["<footer class=\"k2b-settings-page__footer\">", "</footer>"],
12
+ _tmpl$5 = ["<section class=\"", "\" style=\"", "\"", ">", "<div class=\"k2b-settings-page__body\"", ">", "</div>", "</section>"],
13
+ _tmpl$6 = ["<i", " aria-hidden=\"true\"></i>"],
14
+ _tmpl$7 = ["<div class=\"k2b-settings-section__actions\">", "</div>"],
15
+ _tmpl$8 = ["<section class=\"", "\"", "><header class=\"k2b-settings-section__header\">", "<div class=\"k2b-settings-section__heading\"><h2", ">", "</h2>", "</div>", "</header><div class=\"k2b-settings-section__body\">", "</div></section>"],
16
+ _tmpl$9 = ["<section class=\"", "\"", "><header class=\"k2b-settings-group__header\"><div class=\"k2b-settings-group__heading\"><h3", ">", "</h3>", "</div>", "</header><div class=\"k2b-settings-group__body\">", "</div></section>"],
17
+ _tmpl$0 = ["<div class=\"k2b-settings-group__action\">", "</div>"],
18
+ _tmpl$1 = ["<span>", "</span>"],
19
+ _tmpl$10 = ["<div class=\"", "\"><div class=\"k2b-settings-field__copy\"><div class=\"k2b-settings-field__heading\"><strong>", "</strong>", "</div><p", ">", "</p></div>", "", "</div>"],
20
+ _tmpl$11 = ["<p", " class=\"k2b-settings-field__error\" role=\"alert\"><i class=\"ti ti-alert-circle\" aria-hidden=\"true\"></i> ", "</p>"],
21
+ _tmpl$12 = "<i class=\"ti ti-device-floppy\" aria-hidden=\"true\"></i>",
22
+ _tmpl$13 = ["<div class=\"k2b-settings-actions\">", "", "</div>"],
23
+ _tmpl$14 = ["<div class=\"", "\"><p role=\"status\" aria-live=\"polite\"><strong>", "</strong> ", "</p>", "</div>"],
24
+ _tmpl$15 = ["<strong>", "</strong>"],
25
+ _tmpl$16 = ["<p class=\"k2b-settings-panel-footer__status\" role=\"status\" aria-live=\"polite\">", "</p>"],
26
+ _tmpl$17 = ["<div class=\"", "\"><span class=\"k2b-save-status__slot\"><span class=\"k2b-save-status__state\"", " role=\"status\" aria-live=\"polite\"><i class=\"", "\" aria-hidden=\"true\"></i>", "</span></span>", "</div>"];
25
27
  import { children, createMemo, createUniqueId, Show } from "solid-js";
26
28
  import { Button } from "../actions/Button";
27
29
  import { usePromptMessages } from "../feedback/prompt-messages";
28
30
  import { useSettingsMessages } from "./settings-messages";
29
31
  const read = value => typeof value === "function" ? value() : value;
30
32
  export function SettingsPage(props) {
31
- return _$ssr(_tmpl$4, `k2b-settings-page${props.class ? ` ${_$escape(props.class, true)}` : ""}`, _$ssrStyle(props.style), _$escape(_$createComponent(Show, {
33
+ return _$ssr(_tmpl$5, `k2b-settings-page${props.class ? ` ${_$escape(props.class, true)}` : ""}`, _$ssrStyle(props.style), _$ssrAttribute("aria-label", props.header === false && typeof props.title === "string" ? _$escape(props.title, true) : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
32
34
  get when() {
33
- return props.icon;
34
- },
35
- children: icon => _$ssr(_tmpl$5, _$ssrAttribute("class", _$escape(icon(), true), false))
36
- })), _$escape(props.title), _$escape(_$createComponent(Show, {
37
- get when() {
38
- return props.subtitle;
39
- },
40
- get children() {
41
- return _$ssr(_tmpl$, _$escape(props.subtitle));
42
- }
43
- })), _$escape(_$createComponent(Show, {
44
- get when() {
45
- return props.actions;
35
+ return props.header !== false;
46
36
  },
47
37
  get children() {
48
- return _$ssr(_tmpl$2, _$escape(props.actions));
38
+ return _$ssr(_tmpl$3, _$escape(_$createComponent(Show, {
39
+ get when() {
40
+ return props.icon;
41
+ },
42
+ children: icon => _$ssr(_tmpl$6, _$ssrAttribute("class", _$escape(icon(), true), false))
43
+ })), _$escape(props.title), _$escape(_$createComponent(Show, {
44
+ get when() {
45
+ return props.subtitle;
46
+ },
47
+ get children() {
48
+ return _$ssr(_tmpl$, _$escape(props.subtitle));
49
+ }
50
+ })), _$escape(_$createComponent(Show, {
51
+ get when() {
52
+ return props.actions;
53
+ },
54
+ get children() {
55
+ return _$ssr(_tmpl$2, _$escape(props.actions));
56
+ }
57
+ })));
49
58
  }
50
59
  })), _$ssrAttribute("data-scroll-preserve", _$escape(props.scrollPreserveKey, true), false), _$escape(props.children), _$escape(_$createComponent(Show, {
51
60
  get when() {
52
61
  return props.footer;
53
62
  },
54
63
  get children() {
55
- return _$ssr(_tmpl$3, _$escape(props.footer));
64
+ return _$ssr(_tmpl$4, _$escape(props.footer));
56
65
  }
57
66
  })));
58
67
  }
@@ -62,11 +71,11 @@ export function SettingsPage(props) {
62
71
  */
63
72
  export function SettingsSection(props) {
64
73
  const headingId = `k2b-settings-section-${createUniqueId()}`;
65
- return _$ssr(_tmpl$7, `k2b-settings-section${props.class ? ` ${_$escape(props.class, true)}` : ""}`, _$ssrAttribute("aria-labelledby", _$escape(headingId, true), false), _$escape(_$createComponent(Show, {
74
+ return _$ssr(_tmpl$8, `k2b-settings-section${props.class ? ` ${_$escape(props.class, true)}` : ""}`, _$ssrAttribute("aria-labelledby", _$escape(headingId, true), false), _$escape(_$createComponent(Show, {
66
75
  get when() {
67
76
  return props.icon;
68
77
  },
69
- children: icon => _$ssr(_tmpl$5, _$ssrAttribute("class", _$escape(icon(), true), false))
78
+ children: icon => _$ssr(_tmpl$6, _$ssrAttribute("class", _$escape(icon(), true), false))
70
79
  })), _$ssrAttribute("id", _$escape(headingId, true), false), _$escape(props.title), _$escape(_$createComponent(Show, {
71
80
  get when() {
72
81
  return props.subtitle;
@@ -79,7 +88,7 @@ export function SettingsSection(props) {
79
88
  return props.actions;
80
89
  },
81
90
  get children() {
82
- return _$ssr(_tmpl$6, _$escape(props.actions));
91
+ return _$ssr(_tmpl$7, _$escape(props.actions));
83
92
  }
84
93
  })), _$escape(props.children));
85
94
  }
@@ -101,7 +110,7 @@ export const SettingsGroup = props => {
101
110
  const action = createMemo(() => collectSettingsGroupActions(resolved())[0]);
102
111
  const content = createMemo(() => collectSettingsGroupContent(resolved()));
103
112
  const headingId = `k2b-settings-group-${createUniqueId()}`;
104
- return _$ssr(_tmpl$8, `k2b-settings-group${props.class ? ` ${_$escape(props.class, true)}` : ""}`, _$ssrAttribute("aria-labelledby", _$escape(headingId, true), false), _$ssrAttribute("id", _$escape(headingId, true), false), _$escape(props.title), _$escape(_$createComponent(Show, {
113
+ return _$ssr(_tmpl$9, `k2b-settings-group${props.class ? ` ${_$escape(props.class, true)}` : ""}`, _$ssrAttribute("aria-labelledby", _$escape(headingId, true), false), _$ssrAttribute("id", _$escape(headingId, true), false), _$escape(props.title), _$escape(_$createComponent(Show, {
105
114
  get when() {
106
115
  return props.description;
107
116
  },
@@ -112,7 +121,7 @@ export const SettingsGroup = props => {
112
121
  get when() {
113
122
  return action();
114
123
  },
115
- children: slot => _$ssr(_tmpl$9, _$escape(slot().props.children))
124
+ children: slot => _$ssr(_tmpl$0, _$escape(slot().props.children))
116
125
  })), _$escape(content()));
117
126
  };
118
127
  SettingsGroup.Action = SettingsGroupAction;
@@ -133,18 +142,18 @@ export function SettingsField(props) {
133
142
  const control = () => typeof props.children === "function" ? props.children({
134
143
  describedBy
135
144
  }) : props.children;
136
- return _$ssr(_tmpl$1, `k2b-settings-field ${_$escape(props.class ?? "", true)}`, _$escape(props.label), _$escape(_$createComponent(Show, {
145
+ return _$ssr(_tmpl$10, `k2b-settings-field ${_$escape(props.class ?? "", true)}`, _$escape(props.label), _$escape(_$createComponent(Show, {
137
146
  get when() {
138
147
  return _$memo(() => props.changed !== undefined)() && read(props.changed);
139
148
  },
140
149
  get children() {
141
- return _$ssr(_tmpl$0, _$escape(settingsMessages().unsaved));
150
+ return _$ssr(_tmpl$1, _$escape(settingsMessages().unsaved));
142
151
  }
143
152
  })), _$ssrAttribute("id", _$escape(descriptionId, true), false), _$escape(props.description), _$escape(control()), _$escape(_$createComponent(Show, {
144
153
  get when() {
145
154
  return read(props.error);
146
155
  },
147
- children: error => _$ssr(_tmpl$10, _$ssrAttribute("id", _$escape(errorId, true), false), _$escape(error()))
156
+ children: error => _$ssr(_tmpl$11, _$ssrAttribute("id", _$escape(errorId, true), false), _$escape(error()))
148
157
  })));
149
158
  }
150
159
  const SettingsActions = props => {
@@ -154,7 +163,7 @@ const SettingsActions = props => {
154
163
  const loading = () => read(props.loading);
155
164
  const disabled = () => loading() || Boolean(props.disableWithoutChanges && changeCount() === 0);
156
165
  const saveDisabled = () => disabled() || Boolean(props.saveDisabled !== undefined && read(props.saveDisabled));
157
- return _$ssr(_tmpl$12, _$escape(_$createComponent(Button, {
166
+ return _$ssr(_tmpl$13, _$escape(_$createComponent(Button, {
158
167
  variant: "secondary",
159
168
  get disabled() {
160
169
  return disabled();
@@ -182,7 +191,7 @@ const SettingsActions = props => {
182
191
  return props.onSave;
183
192
  },
184
193
  get children() {
185
- return [_$ssr(_tmpl$11), props.saveLabel ?? settingsMessages().saveChanges];
194
+ return [_$ssr(_tmpl$12), props.saveLabel ?? settingsMessages().saveChanges];
186
195
  }
187
196
  })));
188
197
  };
@@ -194,7 +203,7 @@ export function SettingsSaveBar(props) {
194
203
  return changeCount() > 0;
195
204
  },
196
205
  get children() {
197
- return _$ssr(_tmpl$13, `k2b-settings-save-bar ${_$escape(props.class ?? "", true)}`, _$escape(changeCount()), _$escape(settingsMessages().unsavedChangeLabel({
206
+ return _$ssr(_tmpl$14, `k2b-settings-save-bar ${_$escape(props.class ?? "", true)}`, _$escape(changeCount()), _$escape(settingsMessages().unsavedChangeLabel({
198
207
  count: changeCount()
199
208
  })), _$escape(_$createComponent(SettingsActions, props)));
200
209
  }
@@ -203,7 +212,7 @@ export function SettingsSaveBar(props) {
203
212
  export function SettingsPanelFooter(props) {
204
213
  const settingsMessages = useSettingsMessages();
205
214
  const changeCount = () => read(props.changeCount);
206
- return [_$ssr(_tmpl$15, _$escape(_$createComponent(Show, {
215
+ return [_$ssr(_tmpl$16, _$escape(_$createComponent(Show, {
207
216
  get when() {
208
217
  return changeCount() > 0;
209
218
  },
@@ -211,7 +220,7 @@ export function SettingsPanelFooter(props) {
211
220
  return settingsMessages().noUnsavedChanges;
212
221
  },
213
222
  get children() {
214
- return [_$ssr(_tmpl$14, _$escape(changeCount())), " ", settingsMessages().unsavedChangeLabel({
223
+ return [_$ssr(_tmpl$15, _$escape(changeCount())), " ", settingsMessages().unsavedChangeLabel({
215
224
  count: changeCount()
216
225
  })];
217
226
  }
@@ -219,4 +228,72 @@ export function SettingsPanelFooter(props) {
219
228
  disableWithoutChanges: true
220
229
  }))];
221
230
  }
231
+ const SAVE_STATUS_ICON = {
232
+ saved: "ti ti-check",
233
+ draft: "ti ti-pencil",
234
+ saving: "ti ti-loader-2",
235
+ failed: "ti ti-alert-triangle",
236
+ invalid: "ti ti-alert-triangle",
237
+ unknown: "ti ti-help-circle",
238
+ readonly: "ti ti-lock"
239
+ };
240
+
241
+ /**
242
+ * The save status of a settings form at one fixed place, usually the head of its column. It reserves its width, so
243
+ * a change of state never moves the title or the buttons beside it. Problems with one field stay at that field.
244
+ */
245
+ export function SaveStatus(props) {
246
+ const settingsMessages = useSettingsMessages();
247
+ const state = () => read(props.state);
248
+ const text = () => {
249
+ const t = settingsMessages();
250
+ switch (state()) {
251
+ case "saved":
252
+ {
253
+ const at = props.savedAt === undefined ? undefined : read(props.savedAt);
254
+ return at ? t.statusSavedAt({
255
+ time: at
256
+ }) : t.statusSaved;
257
+ }
258
+ case "draft":
259
+ return t.statusDraft;
260
+ case "saving":
261
+ return t.saving;
262
+ case "failed":
263
+ return t.statusFailed;
264
+ case "invalid":
265
+ return t.statusInvalid({
266
+ count: props.invalidCount === undefined ? 1 : read(props.invalidCount)
267
+ });
268
+ case "unknown":
269
+ return t.statusUnknown;
270
+ case "readonly":
271
+ return t.statusReadOnly;
272
+ }
273
+ };
274
+ const hasActions = () => Boolean(props.onSave && props.onDiscard) && state() !== "readonly";
275
+ const pending = () => state() === "draft" || state() === "failed" || state() === "invalid" || state() === "saving";
276
+ return _$ssr(_tmpl$17, `k2b-save-status ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-state", _$escape(state(), true), false), `${_$escape(SAVE_STATUS_ICON[state()], true)}${state() === "saving" ? " k2b-spin" : ""}`, _$escape(text()), _$escape(_$createComponent(Show, {
277
+ get when() {
278
+ return hasActions();
279
+ },
280
+ get children() {
281
+ return _$createComponent(SettingsActions, {
282
+ changeCount: () => pending() ? 1 : 0,
283
+ loading: () => state() === "saving",
284
+ saveDisabled: () => state() === "invalid" || Boolean(props.saveDisabled !== undefined && read(props.saveDisabled)),
285
+ get onDiscard() {
286
+ return props.onDiscard;
287
+ },
288
+ get onSave() {
289
+ return props.onSave;
290
+ },
291
+ get saveLabel() {
292
+ return props.saveLabel ?? settingsMessages().save;
293
+ },
294
+ disableWithoutChanges: true
295
+ });
296
+ }
297
+ })));
298
+ }
222
299
  //# sourceMappingURL=Settings.js.map