@k2b/ui 0.44.0 → 0.46.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 (206) hide show
  1. package/dist/browser/actions/Dropdown.js +8 -1
  2. package/dist/browser/actions/Dropdown.js.map +1 -1
  3. package/dist/browser/actions/FilterChip.js +11 -5
  4. package/dist/browser/actions/FilterChip.js.map +1 -1
  5. package/dist/browser/actions/Tabs.js +3 -4
  6. package/dist/browser/actions/Tabs.js.map +1 -1
  7. package/dist/browser/actions/ViewTabs.js +225 -93
  8. package/dist/browser/actions/ViewTabs.js.map +1 -1
  9. package/dist/browser/actions/menu-sheet.js +2 -3
  10. package/dist/browser/actions/menu-sheet.js.map +1 -1
  11. package/dist/browser/actions/phone-sheet.js +4 -0
  12. package/dist/browser/actions/phone-sheet.js.map +1 -0
  13. package/dist/browser/content/DataTable.js +47 -51
  14. package/dist/browser/content/DataTable.js.map +1 -1
  15. package/dist/browser/content/FileGrid.js +125 -99
  16. package/dist/browser/content/FileGrid.js.map +1 -1
  17. package/dist/browser/feedback/dialog-core.js +5 -1
  18. package/dist/browser/feedback/dialog-core.js.map +1 -1
  19. package/dist/browser/feedback/dialog-tone.js +26 -0
  20. package/dist/browser/feedback/dialog-tone.js.map +1 -0
  21. package/dist/browser/feedback/index.js.map +1 -1
  22. package/dist/browser/feedback/prompt-confirm.js +223 -0
  23. package/dist/browser/feedback/prompt-confirm.js.map +1 -0
  24. package/dist/browser/feedback/prompts.js +195 -343
  25. package/dist/browser/feedback/prompts.js.map +1 -1
  26. package/dist/browser/inputs/ChoiceList.js +45 -17
  27. package/dist/browser/inputs/ChoiceList.js.map +1 -1
  28. package/dist/browser/inputs/DatePicker.js +1647 -334
  29. package/dist/browser/inputs/DatePicker.js.map +1 -1
  30. package/dist/browser/inputs/TextInput.js +5 -4
  31. package/dist/browser/inputs/TextInput.js.map +1 -1
  32. package/dist/browser/inputs/choice.js +36 -9
  33. package/dist/browser/inputs/choice.js.map +1 -1
  34. package/dist/browser/inputs/date-input-messages.js +128 -0
  35. package/dist/browser/inputs/date-input-messages.js.map +1 -0
  36. package/dist/browser/inputs/date-input-status.js +132 -0
  37. package/dist/browser/inputs/date-input-status.js.map +1 -0
  38. package/dist/browser/inputs/date-input.js +334 -0
  39. package/dist/browser/inputs/date-input.js.map +1 -0
  40. package/dist/browser/inputs/date-sheet.js +49 -0
  41. package/dist/browser/inputs/date-sheet.js.map +1 -0
  42. package/dist/browser/internal/reveal-in-scroller.js +28 -0
  43. package/dist/browser/internal/reveal-in-scroller.js.map +1 -0
  44. package/dist/browser/intl/messages.js +6 -0
  45. package/dist/browser/intl/messages.js.map +1 -1
  46. package/dist/browser/layout/AppWorkspace.js +556 -528
  47. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  48. package/dist/browser/layout/BottomSheet.js +3 -0
  49. package/dist/browser/layout/BottomSheet.js.map +1 -1
  50. package/dist/browser/layout/DetailPanel.js +130 -108
  51. package/dist/browser/layout/DetailPanel.js.map +1 -1
  52. package/dist/browser/layout/DetailSections.js +1019 -0
  53. package/dist/browser/layout/DetailSections.js.map +1 -0
  54. package/dist/browser/layout/DetailsToggle.js +32 -0
  55. package/dist/browser/layout/DetailsToggle.js.map +1 -0
  56. package/dist/browser/layout/Discussion.js +3 -0
  57. package/dist/browser/layout/Discussion.js.map +1 -1
  58. package/dist/browser/layout/MobileShell.js +116 -94
  59. package/dist/browser/layout/MobileShell.js.map +1 -1
  60. package/dist/browser/layout/PageTitle.js +33 -10
  61. package/dist/browser/layout/PageTitle.js.map +1 -1
  62. package/dist/browser/layout/PanelDialog.js +167 -132
  63. package/dist/browser/layout/PanelDialog.js.map +1 -1
  64. package/dist/browser/layout/Settings.js +175 -139
  65. package/dist/browser/layout/Settings.js.map +1 -1
  66. package/dist/browser/layout/SettingsModal.js +2 -4
  67. package/dist/browser/layout/SettingsModal.js.map +1 -1
  68. package/dist/browser/layout/app-workspace-controller.js +12 -0
  69. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  70. package/dist/browser/layout/detail-sections-messages.js +123 -0
  71. package/dist/browser/layout/detail-sections-messages.js.map +1 -0
  72. package/dist/browser/layout/index.js +2 -0
  73. package/dist/browser/layout/index.js.map +1 -1
  74. package/dist/browser/surfaces/Avatar.js +3 -1
  75. package/dist/browser/surfaces/Avatar.js.map +1 -1
  76. package/dist/browser/surfaces/AvatarStack.js +95 -61
  77. package/dist/browser/surfaces/AvatarStack.js.map +1 -1
  78. package/dist/browser/surfaces/ChangeList.js +77 -0
  79. package/dist/browser/surfaces/ChangeList.js.map +1 -0
  80. package/dist/browser/surfaces/StatusBadge.js +47 -14
  81. package/dist/browser/surfaces/StatusBadge.js.map +1 -1
  82. package/dist/browser/surfaces/index.js +2 -1
  83. package/dist/browser/surfaces/index.js.map +1 -1
  84. package/dist/browser/text-editor/TextEditor.js +26 -8
  85. package/dist/browser/text-editor/TextEditor.js.map +1 -1
  86. package/dist/ssr/actions/Dropdown.js +6 -0
  87. package/dist/ssr/actions/Dropdown.js.map +1 -1
  88. package/dist/ssr/actions/FilterChip.js +1 -1
  89. package/dist/ssr/actions/FilterChip.js.map +1 -1
  90. package/dist/ssr/actions/Tabs.js +3 -4
  91. package/dist/ssr/actions/Tabs.js.map +1 -1
  92. package/dist/ssr/actions/ViewTabs.js +144 -37
  93. package/dist/ssr/actions/ViewTabs.js.map +1 -1
  94. package/dist/ssr/actions/menu-sheet.js +2 -3
  95. package/dist/ssr/actions/menu-sheet.js.map +1 -1
  96. package/dist/ssr/actions/phone-sheet.js +4 -0
  97. package/dist/ssr/actions/phone-sheet.js.map +1 -0
  98. package/dist/ssr/content/DataTable.js +1 -2
  99. package/dist/ssr/content/DataTable.js.map +1 -1
  100. package/dist/ssr/content/FileGrid.js +36 -22
  101. package/dist/ssr/content/FileGrid.js.map +1 -1
  102. package/dist/ssr/feedback/dialog-core.js +5 -1
  103. package/dist/ssr/feedback/dialog-core.js.map +1 -1
  104. package/dist/ssr/feedback/dialog-tone.js +26 -0
  105. package/dist/ssr/feedback/dialog-tone.js.map +1 -0
  106. package/dist/ssr/feedback/index.js.map +1 -1
  107. package/dist/ssr/feedback/prompt-confirm.js +123 -0
  108. package/dist/ssr/feedback/prompt-confirm.js.map +1 -0
  109. package/dist/ssr/feedback/prompts.js +60 -132
  110. package/dist/ssr/feedback/prompts.js.map +1 -1
  111. package/dist/ssr/inputs/ChoiceList.js +29 -9
  112. package/dist/ssr/inputs/ChoiceList.js.map +1 -1
  113. package/dist/ssr/inputs/DatePicker.js +1183 -166
  114. package/dist/ssr/inputs/DatePicker.js.map +1 -1
  115. package/dist/ssr/inputs/TextInput.js +5 -4
  116. package/dist/ssr/inputs/TextInput.js.map +1 -1
  117. package/dist/ssr/inputs/choice.js +36 -9
  118. package/dist/ssr/inputs/choice.js.map +1 -1
  119. package/dist/ssr/inputs/date-input-messages.js +128 -0
  120. package/dist/ssr/inputs/date-input-messages.js.map +1 -0
  121. package/dist/ssr/inputs/date-input-status.js +132 -0
  122. package/dist/ssr/inputs/date-input-status.js.map +1 -0
  123. package/dist/ssr/inputs/date-input.js +334 -0
  124. package/dist/ssr/inputs/date-input.js.map +1 -0
  125. package/dist/ssr/inputs/date-sheet.js +44 -0
  126. package/dist/ssr/inputs/date-sheet.js.map +1 -0
  127. package/dist/ssr/internal/reveal-in-scroller.js +28 -0
  128. package/dist/ssr/internal/reveal-in-scroller.js.map +1 -0
  129. package/dist/ssr/intl/messages.js +6 -0
  130. package/dist/ssr/intl/messages.js.map +1 -1
  131. package/dist/ssr/layout/AppWorkspace.js +71 -50
  132. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  133. package/dist/ssr/layout/BottomSheet.js +3 -0
  134. package/dist/ssr/layout/BottomSheet.js.map +1 -1
  135. package/dist/ssr/layout/DetailPanel.js +17 -0
  136. package/dist/ssr/layout/DetailPanel.js.map +1 -1
  137. package/dist/ssr/layout/DetailSections.js +762 -0
  138. package/dist/ssr/layout/DetailSections.js.map +1 -0
  139. package/dist/ssr/layout/DetailsToggle.js +29 -0
  140. package/dist/ssr/layout/DetailsToggle.js.map +1 -0
  141. package/dist/ssr/layout/Discussion.js +3 -0
  142. package/dist/ssr/layout/Discussion.js.map +1 -1
  143. package/dist/ssr/layout/MobileShell.js +45 -28
  144. package/dist/ssr/layout/MobileShell.js.map +1 -1
  145. package/dist/ssr/layout/PageTitle.js +29 -6
  146. package/dist/ssr/layout/PageTitle.js.map +1 -1
  147. package/dist/ssr/layout/PanelDialog.js +48 -30
  148. package/dist/ssr/layout/PanelDialog.js.map +1 -1
  149. package/dist/ssr/layout/Settings.js +34 -18
  150. package/dist/ssr/layout/Settings.js.map +1 -1
  151. package/dist/ssr/layout/SettingsModal.js +2 -4
  152. package/dist/ssr/layout/SettingsModal.js.map +1 -1
  153. package/dist/ssr/layout/app-workspace-controller.js +12 -0
  154. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  155. package/dist/ssr/layout/detail-sections-messages.js +123 -0
  156. package/dist/ssr/layout/detail-sections-messages.js.map +1 -0
  157. package/dist/ssr/layout/index.js +2 -0
  158. package/dist/ssr/layout/index.js.map +1 -1
  159. package/dist/ssr/surfaces/Avatar.js +3 -1
  160. package/dist/ssr/surfaces/Avatar.js.map +1 -1
  161. package/dist/ssr/surfaces/AvatarStack.js +61 -32
  162. package/dist/ssr/surfaces/AvatarStack.js.map +1 -1
  163. package/dist/ssr/surfaces/ChangeList.js +37 -0
  164. package/dist/ssr/surfaces/ChangeList.js.map +1 -0
  165. package/dist/ssr/surfaces/StatusBadge.js +26 -8
  166. package/dist/ssr/surfaces/StatusBadge.js.map +1 -1
  167. package/dist/ssr/surfaces/index.js +2 -1
  168. package/dist/ssr/surfaces/index.js.map +1 -1
  169. package/dist/ssr/text-editor/TextEditor.js +26 -8
  170. package/dist/ssr/text-editor/TextEditor.js.map +1 -1
  171. package/dist/styles.css +1 -1
  172. package/dist/types/actions/FilterChip.d.ts +4 -0
  173. package/dist/types/actions/ViewTabs.d.ts +11 -1
  174. package/dist/types/actions/menu-sheet.d.ts +1 -3
  175. package/dist/types/actions/phone-sheet.d.ts +3 -0
  176. package/dist/types/content/FileGrid.d.ts +6 -0
  177. package/dist/types/feedback/dialog-tone.d.ts +10 -0
  178. package/dist/types/feedback/index.d.ts +1 -0
  179. package/dist/types/feedback/prompt-confirm.d.ts +19 -0
  180. package/dist/types/feedback/prompts.d.ts +4 -8
  181. package/dist/types/inputs/ChoiceList.d.ts +7 -3
  182. package/dist/types/inputs/DatePicker.d.ts +13 -3
  183. package/dist/types/inputs/TextInput.d.ts +1 -1
  184. package/dist/types/inputs/choice.d.ts +9 -3
  185. package/dist/types/inputs/date-input-messages.d.ts +64 -0
  186. package/dist/types/inputs/date-input-status.d.ts +35 -0
  187. package/dist/types/inputs/date-input.d.ts +90 -0
  188. package/dist/types/inputs/date-sheet.d.ts +14 -0
  189. package/dist/types/internal/reveal-in-scroller.d.ts +6 -0
  190. package/dist/types/intl/messages.d.ts +3 -0
  191. package/dist/types/layout/AppWorkspace.d.ts +14 -0
  192. package/dist/types/layout/BottomSheet.d.ts +3 -0
  193. package/dist/types/layout/DetailPanel.d.ts +9 -0
  194. package/dist/types/layout/DetailSections.d.ts +156 -0
  195. package/dist/types/layout/DetailsToggle.d.ts +13 -0
  196. package/dist/types/layout/PageTitle.d.ts +7 -0
  197. package/dist/types/layout/PanelDialog.d.ts +3 -0
  198. package/dist/types/layout/Settings.d.ts +17 -1
  199. package/dist/types/layout/detail-sections-messages.d.ts +59 -0
  200. package/dist/types/layout/index.d.ts +5 -2
  201. package/dist/types/surfaces/Avatar.d.ts +3 -0
  202. package/dist/types/surfaces/AvatarStack.d.ts +20 -1
  203. package/dist/types/surfaces/ChangeList.d.ts +20 -0
  204. package/dist/types/surfaces/StatusBadge.d.ts +5 -0
  205. package/dist/types/surfaces/index.d.ts +5 -3
  206. package/package.json +3 -3
@@ -1,3 +1,4 @@
1
+ import { memo as _$memo } from "solid-js/web";
1
2
  import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
2
3
  import { createComponent as _$createComponent } from "solid-js/web";
3
4
  import { ssr as _$ssr } from "solid-js/web";
@@ -5,13 +6,15 @@ import { escape as _$escape } from "solid-js/web";
5
6
  var _tmpl$ = ["<span class=\"k2b-view-tabs__count\">", "</span>"],
6
7
  _tmpl$2 = ["<span class=\"k2b-view-tabs__label k2b-truncate-fade\">", "</span>"],
7
8
  _tmpl$3 = ["<i", " aria-hidden=\"true\"></i>"],
8
- _tmpl$4 = ["<span class=\"k2b-view-tabs__label\">", "</span>"],
9
- _tmpl$5 = ["<span class=\"k2b-view-tabs__more-count\" data-level=\"wide\">+", "</span>"],
10
- _tmpl$6 = ["<span class=\"k2b-view-tabs__more-count\" data-level=\"compact\">+", "</span>"],
11
- _tmpl$7 = "<i class=\"ti ti-chevron-down\" aria-hidden=\"true\"></i>",
12
- _tmpl$8 = ["<li class=\"k2b-view-tabs__more\"", ">", "</li>"],
13
- _tmpl$9 = ["<nav class=\"", "\"", "><ul class=\"k2b-view-tabs__list\" data-scroll-fade-mode=\"both\" data-scroll-fade-axis=\"horizontal\">", "", "</ul><script>", "</script></nav>"],
14
- _tmpl$0 = ["<li class=\"k2b-view-tabs__item\"", "><a class=\"k2b-view-tabs__tab\"", ">", "</a></li>"];
9
+ _tmpl$4 = ["<span class=\"k2b-view-tabs__badge\">", "</span>"],
10
+ _tmpl$5 = ["<li class=\"k2b-view-tabs__item\"", "><a class=\"k2b-view-tabs__tab\"", ">", "</a></li>"],
11
+ _tmpl$6 = ["<span class=\"k2b-view-tabs__label\">", "</span>"],
12
+ _tmpl$7 = ["<span class=\"k2b-view-tabs__more-count\" data-level=\"wide\">+", "</span>"],
13
+ _tmpl$8 = ["<span class=\"k2b-view-tabs__more-count\" data-level=\"compact\">+", "</span>"],
14
+ _tmpl$9 = "<span class=\"k2b-view-tabs__more-count\" data-level=\"fit\"></span>",
15
+ _tmpl$0 = "<i class=\"ti ti-chevron-down\" aria-hidden=\"true\"></i>",
16
+ _tmpl$1 = ["<li class=\"k2b-view-tabs__more\"", ">", "</li>"],
17
+ _tmpl$10 = ["<nav class=\"", "\"", "><ul class=\"k2b-view-tabs__list\" data-scroll-fade-mode=\"both\" data-scroll-fade-axis=\"horizontal\"", ">", "", "", "</ul><script>", "</script></nav>"];
15
18
  import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show } from "solid-js";
16
19
  import { useUiMessages } from "../intl/messages";
17
20
  import { createScrollFade, scrollFadeEdges } from "../layout/scroll-fade";
@@ -20,10 +23,14 @@ import Dropdown from "./Dropdown";
20
23
  export function viewTabsLayout(items, current, max = 5) {
21
24
  // At least one slot keeps the current view visible, including for an invalid limit.
22
25
  const limit = Number.isFinite(max) ? Math.max(1, Math.floor(max)) : 5;
26
+ // Pinned views never overflow, so they take no slot.
27
+ const flowing = items.flatMap((item, index) => item.pinned ? [] : [index]);
28
+ const pinned = items.flatMap((item, index) => item.pinned ? [index] : []);
23
29
  const currentIndex = items.findIndex(item => item.id === current);
30
+ const currentPosition = flowing.indexOf(currentIndex);
24
31
  const visible = size => {
25
- const indices = items.slice(0, size).map((_, index) => index);
26
- if (currentIndex >= size) indices[indices.length - 1] = currentIndex;
32
+ const indices = flowing.slice(0, size);
33
+ if (currentPosition >= size) indices[indices.length - 1] = currentIndex;
27
34
  return indices;
28
35
  };
29
36
  const wide = visible(limit);
@@ -31,14 +38,50 @@ export function viewTabsLayout(items, current, max = 5) {
31
38
  return {
32
39
  wide,
33
40
  compact,
34
- wideHidden: items.length - wide.length,
35
- compactHidden: items.length - compact.length
41
+ pinned,
42
+ wideHidden: flowing.length - wide.length,
43
+ compactHidden: flowing.length - compact.length
36
44
  };
37
45
  }
38
46
 
39
- /** Self-contained: the server script embeds this before hydration. */
47
+ /**
48
+ * Self-contained: the server script embeds this before hydration. A fitting strip first keeps only the views that fit
49
+ * its room and counts the rest in More; then the open or focused view scrolls into the strip.
50
+ */
40
51
  function settleViewTabs(list, edges, focused) {
41
52
  if (!(list instanceof HTMLElement)) return;
53
+ const more = list.querySelector(":scope > .k2b-view-tabs__more");
54
+ const fitCount = more?.querySelector('.k2b-view-tabs__more-count[data-level="fit"]');
55
+ const flowing = Array.from(list.querySelectorAll(":scope > .k2b-view-tabs__item:not([data-pinned])"));
56
+ for (const item of flowing) item.removeAttribute("data-fit-hidden");
57
+ more?.removeAttribute("data-fit");
58
+ // The shown entries against the strip's inner room, whatever its scroll and padding.
59
+ const overflows = () => {
60
+ const boxes = Array.from(list.children).filter(child => child instanceof HTMLElement && child.tagName === "LI" && child.getClientRects().length > 0).map(child => child.getBoundingClientRect());
61
+ if (boxes.length === 0) return false;
62
+ const style = getComputedStyle(list);
63
+ const room = list.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);
64
+ return Math.max(...boxes.map(box => box.right)) - Math.min(...boxes.map(box => box.left)) > room + 0.5;
65
+ };
66
+ // The phone head row keeps its scrolling strip: its CSS turns fitting off.
67
+ const fits = list.hasAttribute("data-fit-room") && getComputedStyle(list).getPropertyValue("--k2b-view-tabs-fit").trim() !== "none";
68
+ if (fits && more && fitCount && overflows()) {
69
+ // Count from the level on screen: the compact strip already keeps fewer views than the wide one.
70
+ const compact = more.querySelector('.k2b-view-tabs__more-count[data-level="compact"]');
71
+ const compactShown = Boolean(compact && getComputedStyle(compact).display !== "none");
72
+ const behind = Number(compactShown ? more.dataset.behindCompact : more.dataset.behindWide) || 0;
73
+ // The last views on screen leave first; the open view stays.
74
+ const candidates = flowing.filter(item => item.getClientRects().length > 0 && !item.querySelector("[aria-current=page]")).reverse();
75
+ more.setAttribute("data-fit", "");
76
+ let hidden = 0;
77
+ for (const item of candidates) {
78
+ item.setAttribute("data-fit-hidden", "");
79
+ hidden += 1;
80
+ fitCount.textContent = `+${behind + hidden}`;
81
+ if (!overflows()) break;
82
+ }
83
+ if (hidden === 0) more.removeAttribute("data-fit");
84
+ }
42
85
  const current = focused ?? list.querySelector(".k2b-view-tabs__tab[aria-current=page]");
43
86
  if (current && current.getClientRects().length > 0 && list.scrollWidth - list.clientWidth > 1) {
44
87
  const bounds = list.getBoundingClientRect();
@@ -63,9 +106,20 @@ function settleViewTabs(list, edges, focused) {
63
106
  const fade = edges(list);
64
107
  if (fade) list.setAttribute("data-scroll-fade", fade);else list.removeAttribute("data-scroll-fade");
65
108
  }
66
- /** Also embedded, so it must not close over module state. */
109
+ /**
110
+ * Also embedded, so it must not close over module state.
111
+ *
112
+ * A fitting strip converges by construction. It fits again only when the strip renders, web fonts finish loading, or
113
+ * a notification finds another width of the head lead or of an entry that the last fit left shown. A fit only shows
114
+ * and hides entries: the lead is an inline-size container that takes no width from the strip, and every entry keeps
115
+ * its own width (`flex: none`), so the notifications a fit causes find the widths it ended with and stop there.
116
+ */
67
117
  function watchViewTabs(list, settle, edges) {
68
118
  if (!(list instanceof HTMLElement)) return;
119
+ const fits = list.hasAttribute("data-fit-room");
120
+ const roomOf = () => list.closest(".k2b-app-workspace__head-lead");
121
+ // What a fit depends on: the lead's width and each shown entry's width, which fonts, icons and labels change.
122
+ const measured = () => [roomOf()?.clientWidth ?? -1, ...Array.from(list.children, child => child.getBoundingClientRect().width)].join(" ");
69
123
  // A DOM property survives hydration: one observer and focus listener per reused list.
70
124
  let watch = list.__k2bViewTabsWatch;
71
125
  if (!watch) {
@@ -76,29 +130,66 @@ function watchViewTabs(list, settle, edges) {
76
130
  settle(list, edges, event.target);
77
131
  }
78
132
  };
133
+ const settleNow = () => {
134
+ const focused = document.activeElement;
135
+ if (watch?.preferCurrent === false && focused instanceof HTMLElement && list.contains(focused) && focused.matches(".k2b-view-tabs__tab:focus-visible")) settle(list, edges, focused);else settle(list, edges);
136
+ };
137
+ // Head actions parse after the views; settle again after their layout, before paint, and on later resizes.
138
+ const connect = () => {
139
+ if (!watch) return;
140
+ watch.resize.disconnect();
141
+ watch.resize.observe(list);
142
+ for (const child of Array.from(list.children)) watch.resize.observe(child);
143
+ const room = roomOf();
144
+ if (fits && room) watch.resize.observe(room);
145
+ };
146
+ // Fitting resizes the strip and possibly the head, which other observers may watch. Inside a ResizeObserver
147
+ // callback that would leave their notifications undelivered, so a fit runs in the next frame, still before it paints.
148
+ const schedule = () => {
149
+ if (!watch || watch.frame) return;
150
+ watch.frame = requestAnimationFrame(() => {
151
+ if (!watch) return;
152
+ watch.frame = 0;
153
+ if (!list.isConnected) return;
154
+ settleNow();
155
+ watch.room = measured();
156
+ });
157
+ };
79
158
  watch = {
80
159
  resize: new ResizeObserver(() => {
81
- const focused = document.activeElement;
82
- if (watch?.preferCurrent === false && focused instanceof HTMLElement && list.contains(focused) && focused.matches(".k2b-view-tabs__tab:focus-visible")) settle(list, edges, focused);else settle(list, edges);
160
+ // A scrolling strip only scrolls and settles at once.
161
+ if (!fits) return settleNow();
162
+ // Heights follow the fit, and hidden entries measure 0 by its own doing: only another measure counts.
163
+ if (watch && measured() !== watch.room) watch.schedule();
83
164
  }),
84
165
  focus,
85
- preferCurrent: true
166
+ preferCurrent: true,
167
+ connect,
168
+ schedule,
169
+ frame: 0,
170
+ room: "",
171
+ fonts: () => watch?.schedule()
86
172
  };
87
173
  Object.defineProperty(list, "__k2bViewTabsWatch", {
88
174
  value: watch,
89
175
  configurable: true
90
176
  });
91
177
  list.addEventListener("focusin", focus);
178
+ // A first visit can paint before the web fonts load; the server frame and the client then fit the same metrics.
179
+ if (fits && document.fonts) {
180
+ document.fonts.addEventListener("loadingdone", watch.fonts);
181
+ void document.fonts.ready.then(watch.fonts);
182
+ }
92
183
  }
93
184
  watch.preferCurrent = true;
94
- const {
95
- resize
96
- } = watch;
97
- resize.disconnect();
98
- // Head actions parse after the views; settle again after their layout, before paint, and on later resizes.
99
- resize.observe(list);
100
- for (const child of Array.from(list.children)) resize.observe(child);
185
+ watch.connect();
101
186
  settle(list, edges);
187
+ if (fits) {
188
+ watch.room = measured();
189
+ // The head's actions parse after the views. A frame's animation callbacks run before its first layout and
190
+ // paint, so this fit also counts their room in the first frame.
191
+ watch.schedule();
192
+ }
102
193
  return watch;
103
194
  }
104
195
  const VIEW_TABS_SCRIPT = `(${watchViewTabs})(document.currentScript&&document.currentScript.previousElementSibling,${settleViewTabs},${scrollFadeEdges})`;
@@ -125,6 +216,8 @@ export function ViewTabs(props) {
125
216
  onCleanup(() => {
126
217
  disposed = true;
127
218
  watch?.resize.disconnect();
219
+ if (watch?.frame) cancelAnimationFrame(watch.frame);
220
+ if (watch) document.fonts?.removeEventListener("loadingdone", watch.fonts);
128
221
  if (list && watch) {
129
222
  list.removeEventListener("focusin", watch.focus);
130
223
  Reflect.deleteProperty(list, "__k2bViewTabsWatch");
@@ -132,14 +225,17 @@ export function ViewTabs(props) {
132
225
  });
133
226
  });
134
227
  // User effects run after Solid's render effects, including For and aria-current, in the same task.
135
- createEffect(on(() => props.current, () => watchViewTabs(list ?? null, settleViewTabs, scrollFadeEdges), {
228
+ // What the strip shows: another open view, view, label, count or badge can change what fits. Equal content in a new
229
+ // array settles nothing again.
230
+ const shown = createMemo(() => [props.current ?? "", layout().wide.join(","), ...props.items.map(item => `${item.id}\u0000${item.label}\u0000${item.count ?? ""}\u0000${item.badge ?? ""}`)].join("\u0001"));
231
+ createEffect(on(shown, () => watchViewTabs(list ?? null, settleViewTabs, scrollFadeEdges), {
136
232
  defer: true
137
233
  }));
138
234
  const [open, setOpen] = createSignal(false);
139
235
  const [menuItems, setMenuItems] = createSignal([]);
140
236
  const onOpenChange = next => {
141
237
  if (next) {
142
- setMenuItems(props.items.filter((item, index) => item.id !== props.current && (!layout().wide.includes(index) || stripItems.get(index)?.getClientRects().length === 0)).map(item => ({
238
+ setMenuItems(props.items.filter((item, index) => !item.pinned && item.id !== props.current && (!layout().wide.includes(index) || stripItems.get(index)?.getClientRects().length === 0)).map(item => ({
143
239
  href: item.href,
144
240
  label: item.label,
145
241
  icon: item.icon,
@@ -160,22 +256,28 @@ export function ViewTabs(props) {
160
256
  get children() {
161
257
  return _$ssr(_tmpl$, _$escape(item.count));
162
258
  }
259
+ }), _$createComponent(Show, {
260
+ get when() {
261
+ return item.badge;
262
+ },
263
+ children: badge => _$ssr(_tmpl$4, _$escape(badge()))
163
264
  })];
164
- return _$ssr(_tmpl$9, `k2b-view-tabs ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$escape(_$createComponent(For, {
265
+ const stripItem = index => {
266
+ const item = () => props.items[index];
267
+ onCleanup(() => stripItems.delete(index));
268
+ return _$ssr(_tmpl$5, _$ssrAttribute("data-view-tab", _$escape(item().id, true), false) + _$ssrAttribute("data-pinned", item().pinned ? "" : _$escape(undefined, true), false) + _$ssrAttribute("data-compact-hidden", !item().pinned && !layout().compact.includes(index) ? "" : _$escape(undefined, true), false), _$ssrAttribute("href", _$escape(item().href, true), false) + _$ssrAttribute("aria-current", item().id === props.current ? "page" : _$escape(undefined, true), false), _$escape(tabContent(item())));
269
+ };
270
+ return _$ssr(_tmpl$10, `k2b-view-tabs ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$ssrAttribute("data-fit-room", props.fit ? "" : _$escape(undefined, true), false), _$escape(_$createComponent(For, {
165
271
  get each() {
166
272
  return layout().wide;
167
273
  },
168
- children: index => {
169
- const item = () => props.items[index];
170
- onCleanup(() => stripItems.delete(index));
171
- return _$ssr(_tmpl$0, _$ssrAttribute("data-view-tab", _$escape(item().id, true), false) + _$ssrAttribute("data-compact-hidden", !layout().compact.includes(index) ? "" : _$escape(undefined, true), false), _$ssrAttribute("href", _$escape(item().href, true), false) + _$ssrAttribute("aria-current", item().id === props.current ? "page" : _$escape(undefined, true), false), _$escape(tabContent(item())));
172
- }
274
+ children: stripItem
173
275
  })), _$escape(_$createComponent(Show, {
174
276
  get when() {
175
- return layout().compactHidden + layout().wideHidden > 0;
277
+ return layout().compactHidden + layout().wideHidden > 0 || props.fit && layout().wide.length > 1;
176
278
  },
177
279
  get children() {
178
- return _$ssr(_tmpl$8, _$ssrAttribute("data-wide", layout().wideHidden > 0 ? "" : _$escape(undefined, true), false), _$escape(_$createComponent(Dropdown.Root, {
280
+ return _$ssr(_tmpl$1, _$ssrAttribute("data-wide", layout().wideHidden > 0 ? "" : _$escape(undefined, true), false) + _$ssrAttribute("data-behind-wide", _$escape(layout().wideHidden, true), false) + _$ssrAttribute("data-behind-compact", _$escape(layout().compactHidden, true), false), _$escape(_$createComponent(Dropdown.Root, {
179
281
  get items() {
180
282
  return menuItems();
181
283
  },
@@ -194,26 +296,31 @@ export function ViewTabs(props) {
194
296
  return messages().moreViews;
195
297
  },
196
298
  get children() {
197
- return [_$ssr(_tmpl$4, _$escape(messages().more)), _$createComponent(Show, {
299
+ return [_$ssr(_tmpl$6, _$escape(messages().more)), _$createComponent(Show, {
198
300
  get when() {
199
301
  return layout().wideHidden > 0;
200
302
  },
201
303
  get children() {
202
- return _$ssr(_tmpl$5, _$escape(layout().wideHidden));
304
+ return _$ssr(_tmpl$7, _$escape(layout().wideHidden));
203
305
  }
204
306
  }), _$createComponent(Show, {
205
307
  get when() {
206
308
  return layout().compactHidden > 0;
207
309
  },
208
310
  get children() {
209
- return _$ssr(_tmpl$6, _$escape(layout().compactHidden));
311
+ return _$ssr(_tmpl$8, _$escape(layout().compactHidden));
210
312
  }
211
- }), _$ssr(_tmpl$7)];
313
+ }), _$ssr(_tmpl$9), _$ssr(_tmpl$0)];
212
314
  }
213
315
  });
214
316
  }
215
317
  })));
216
318
  }
319
+ })), _$escape(_$createComponent(For, {
320
+ get each() {
321
+ return layout().pinned;
322
+ },
323
+ children: stripItem
217
324
  })), VIEW_TABS_SCRIPT);
218
325
  }
219
326
  //# sourceMappingURL=ViewTabs.js.map
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["actions/ViewTabs.tsx"],"sourcesContent":["import { createEffect, createMemo, createSignal, For, type JSX, on, onCleanup, onMount, Show } from \"solid-js\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { createScrollFade, scrollFadeEdges } from \"../layout/scroll-fade\";\nimport Dropdown, { type DropdownItem } from \"./Dropdown\";\n\nexport type ViewTab = { id: string; label: string; href: string; icon?: string; count?: number | string };\nexport type ViewTabsProps = {\n /** Accessible name of the navigation landmark, e.g. \"Views\". */\n label: string;\n items: readonly ViewTab[];\n /** Id of the open view; the server reads it from `?view=`. Unknown ids select nothing. */\n current: string | undefined;\n /** Tabs shown before More (default 5). */\n max?: number;\n class?: string;\n};\n\n/** Two deterministic strip levels; only opening the menu reads CSS visibility. */\nexport function viewTabsLayout(items: readonly ViewTab[], current: string | undefined, max = 5) {\n // At least one slot keeps the current view visible, including for an invalid limit.\n const limit = Number.isFinite(max) ? Math.max(1, Math.floor(max)) : 5;\n const currentIndex = items.findIndex((item) => item.id === current);\n const visible = (size: number) => {\n const indices = items.slice(0, size).map((_, index) => index);\n if (currentIndex >= size) indices[indices.length - 1] = currentIndex;\n return indices;\n };\n const wide = visible(limit);\n const compact = visible(Math.min(limit, 3));\n return { wide, compact, wideHidden: items.length - wide.length, compactHidden: items.length - compact.length };\n}\n\n/** Self-contained: the server script embeds this before hydration. */\nfunction settleViewTabs(list: Element | null, edges: (element: HTMLElement) => string | undefined, focused?: HTMLElement) {\n if (!(list instanceof HTMLElement)) return;\n const current = focused ?? list.querySelector(\".k2b-view-tabs__tab[aria-current=page]\");\n if (current && current.getClientRects().length > 0 && list.scrollWidth - list.clientWidth > 1) {\n const bounds = list.getBoundingClientRect();\n const item = current.getBoundingClientRect();\n const fade = edges(list);\n const style = getComputedStyle(list);\n const left = fade === \"top\" || fade === \"both\" ? parseFloat(style.scrollPaddingLeft) || 0 : 0;\n const right = fade === \"bottom\" || fade === \"both\" ? parseFloat(style.scrollPaddingRight) || 0 : 0;\n if (item.left < bounds.left + left || item.right > bounds.right - right) {\n let offset = item.left - bounds.left - (bounds.width - item.width) / 2;\n if (focused) {\n // Reserve padding at the destination edge; native clamping drops it when that end is reached.\n offset =\n item.left < bounds.left + left\n ? item.left - bounds.left - (parseFloat(style.scrollPaddingLeft) || 0)\n : item.right - bounds.right + (parseFloat(style.scrollPaddingRight) || 0);\n }\n // Only the strip moves: scrollIntoView would also scroll the page. Instant keeps the first frame free of animation.\n list.scrollTo({ left: list.scrollLeft + offset, behavior: \"instant\" });\n }\n }\n const fade = edges(list);\n if (fade) list.setAttribute(\"data-scroll-fade\", fade);\n else list.removeAttribute(\"data-scroll-fade\");\n}\n\ntype WatchedViewTabs = HTMLElement & {\n __k2bViewTabsWatch?: { resize: ResizeObserver; focus: (event: FocusEvent) => void; preferCurrent: boolean };\n};\n\n/** Also embedded, so it must not close over module state. */\nfunction watchViewTabs(list: WatchedViewTabs | null, settle: typeof settleViewTabs, edges: (element: HTMLElement) => string | undefined) {\n if (!(list instanceof HTMLElement)) return;\n // A DOM property survives hydration: one observer and focus listener per reused list.\n let watch = list.__k2bViewTabsWatch;\n if (!watch) {\n const focus = (event: FocusEvent) => {\n // Pointer focus must not move the target under the pointer.\n if (watch && event.target instanceof HTMLElement && event.target.matches(\".k2b-view-tabs__tab:focus-visible\")) {\n watch.preferCurrent = false;\n settle(list, edges, event.target);\n }\n };\n watch = {\n resize: new ResizeObserver(() => {\n const focused = document.activeElement;\n if (\n watch?.preferCurrent === false &&\n focused instanceof HTMLElement &&\n list.contains(focused) &&\n focused.matches(\".k2b-view-tabs__tab:focus-visible\")\n )\n settle(list, edges, focused);\n else settle(list, edges);\n }),\n focus,\n preferCurrent: true,\n };\n Object.defineProperty(list, \"__k2bViewTabsWatch\", { value: watch, configurable: true });\n list.addEventListener(\"focusin\", focus);\n }\n watch.preferCurrent = true;\n const { resize } = watch;\n resize.disconnect();\n // Head actions parse after the views; settle again after their layout, before paint, and on later resizes.\n resize.observe(list);\n for (const child of Array.from(list.children)) resize.observe(child);\n settle(list, edges);\n return watch;\n}\nconst VIEW_TABS_SCRIPT = `(${watchViewTabs})(document.currentScript&&document.currentScript.previousElementSibling,${settleViewTabs},${scrollFadeEdges})`;\n\n/**\n * URL-backed object views. Every view is an ordinary document link, in the strip and under More, so reload, back,\n * and forward keep the view the URL names.\n */\nexport function ViewTabs(props: ViewTabsProps): JSX.Element {\n const messages = useUiMessages();\n const layout = createMemo(() => viewTabsLayout(props.items, props.current, props.max));\n const stripItems = new Map<number, HTMLLIElement>();\n let list: HTMLUListElement | undefined;\n createScrollFade(\n () => list,\n () => true,\n );\n onMount(() => {\n const watch = watchViewTabs(list ?? null, settleViewTabs, scrollFadeEdges);\n let disposed = false;\n // Later mount effects can invalidate WebKit's scroll layout after this\n // initial settle. Finish once the mount task is complete, before its first\n // frame; ResizeObserver alone runs too late for that frame.\n queueMicrotask(() => {\n if (!disposed && watch?.preferCurrent) settleViewTabs(list ?? null, scrollFadeEdges);\n });\n onCleanup(() => {\n disposed = true;\n watch?.resize.disconnect();\n if (list && watch) {\n list.removeEventListener(\"focusin\", watch.focus);\n Reflect.deleteProperty(list, \"__k2bViewTabsWatch\");\n }\n });\n });\n // User effects run after Solid's render effects, including For and aria-current, in the same task.\n createEffect(\n on(\n () => props.current,\n () => watchViewTabs(list ?? null, settleViewTabs, scrollFadeEdges),\n { defer: true },\n ),\n );\n const [open, setOpen] = createSignal(false);\n const [menuItems, setMenuItems] = createSignal<DropdownItem[]>([]);\n const onOpenChange = (next: boolean) => {\n if (next) {\n setMenuItems(\n props.items\n .filter(\n (item, index) =>\n item.id !== props.current && (!layout().wide.includes(index) || stripItems.get(index)?.getClientRects().length === 0),\n )\n .map((item) => ({\n href: item.href,\n label: item.label,\n icon: item.icon,\n trailing: item.count !== undefined ? <span class=\"k2b-view-tabs__count\">{item.count}</span> : undefined,\n })),\n );\n }\n setOpen(next);\n };\n const tabContent = (item: ViewTab) => (\n <>\n <Show when={item.icon}>{(icon) => <i class={icon()} aria-hidden=\"true\" />}</Show>\n <span class=\"k2b-view-tabs__label k2b-truncate-fade\">{item.label}</span>\n <Show when={item.count !== undefined}>\n <span class=\"k2b-view-tabs__count\">{item.count}</span>\n </Show>\n </>\n );\n return (\n <nav class={`k2b-view-tabs ${props.class ?? \"\"}`} aria-label={props.label}>\n <ul ref={list} class=\"k2b-view-tabs__list\" data-scroll-fade-mode=\"both\" data-scroll-fade-axis=\"horizontal\">\n <For each={layout().wide}>\n {(index) => {\n const item = () => props.items[index]!;\n onCleanup(() => stripItems.delete(index));\n return (\n <li\n ref={(element) => stripItems.set(index, element)}\n class=\"k2b-view-tabs__item\"\n data-view-tab={item().id}\n data-compact-hidden={!layout().compact.includes(index) ? \"\" : undefined}\n >\n <a class=\"k2b-view-tabs__tab\" href={item().href} aria-current={item().id === props.current ? \"page\" : undefined}>\n {tabContent(item())}\n </a>\n </li>\n );\n }}\n </For>\n <Show when={layout().compactHidden + layout().wideHidden > 0}>\n <li class=\"k2b-view-tabs__more\" data-wide={layout().wideHidden > 0 ? \"\" : undefined}>\n <Dropdown.Root items={menuItems()} open={open()} onOpenChange={onOpenChange} label={messages().moreViews}>\n <Dropdown.Trigger appearance=\"plain\" class=\"k2b-view-tabs__tab k2b-view-tabs__more-trigger\" label={messages().moreViews}>\n <span class=\"k2b-view-tabs__label\">{messages().more}</span>\n <Show when={layout().wideHidden > 0}>\n <span class=\"k2b-view-tabs__more-count\" data-level=\"wide\">\n +{layout().wideHidden}\n </span>\n </Show>\n <Show when={layout().compactHidden > 0}>\n <span class=\"k2b-view-tabs__more-count\" data-level=\"compact\">\n +{layout().compactHidden}\n </span>\n </Show>\n <i class=\"ti ti-chevron-down\" aria-hidden=\"true\" />\n </Dropdown.Trigger>\n </Dropdown.Root>\n </li>\n </Show>\n </ul>\n <script innerHTML={VIEW_TABS_SCRIPT} />\n </nav>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,SAAS,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,EAAY,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,QAAQ,UAAU;AAC9G,SAAS,aAAa,QAAQ,kBAAkB;AAChD,SAAS,gBAAgB,EAAE,eAAe,QAAQ,uBAAuB;AACzE,OAAO,QAAQ,MAA6B,YAAY;AAcxD;AACA,OAAO,SAAS,cAAc,CAAC,KAAyB,EAAE,OAA2B,EAAE,GAAG,GAAG,CAAC,EAAE;EAC9F;EACA,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;EACrE,MAAM,YAAY,GAAG,KAAK,CAAC,SAAS,CAAE,IAAI,IAAK,IAAI,CAAC,EAAE,KAAK,OAAO,CAAC;EACnE,MAAM,OAAO,GAAI,IAAY,IAAK;IAChC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,KAAK,CAAC;IAC7D,IAAI,YAAY,IAAI,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,YAAY;IACpE,OAAO,OAAO;EAChB,CAAC;EACD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC;EAC3B,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;EAC3C,OAAO;IAAE,IAAI;IAAE,OAAO;IAAE,UAAU,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM;IAAE,aAAa,EAAE,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC;EAAO,CAAC;AAChH;;AAEA;AACA,SAAS,cAAc,CAAC,IAAoB,EAAE,KAAmD,EAAE,OAAqB,EAAE;EACxH,IAAI,EAAE,IAAI,YAAY,WAAW,CAAC,EAAE;EACpC,MAAM,OAAO,GAAG,OAAO,IAAI,IAAI,CAAC,aAAa,CAAC,wCAAwC,CAAC;EACvF,IAAI,OAAO,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,GAAG,CAAC,EAAE;IAC7F,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC;IAC7F,MAAM,KAAK,GAAG,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC;IAClG,IAAI,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,GAAG,KAAK,EAAE;MACvE,IAAI,MAAM,GAAG,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC;MACtE,IAAI,OAAO,EAAE;QACX;QACA,MAAM,GACJ,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,IAAI,GAC1B,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,UAAU,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,GACpE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;MAC/E;MACA;MACA,IAAI,CAAC,QAAQ,CAAC;QAAE,IAAI,EAAE,IAAI,CAAC,UAAU,GAAG,MAAM;QAAE,QAAQ,EAAE;MAAU,CAAC,CAAC;IACxE;EACF;EACA,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;EACxB,IAAI,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC,KACjD,IAAI,CAAC,eAAe,CAAC,kBAAkB,CAAC;AAC/C;AAMA;AACA,SAAS,aAAa,CAAC,IAA4B,EAAE,MAA6B,EAAE,KAAmD,EAAE;EACvI,IAAI,EAAE,IAAI,YAAY,WAAW,CAAC,EAAE;EACpC;EACA,IAAI,KAAK,GAAG,IAAI,CAAC,kBAAkB;EACnC,IAAI,CAAC,KAAK,EAAE;IACV,MAAM,KAAK,GAAI,KAAiB,IAAK;MACnC;MACA,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,YAAY,WAAW,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,mCAAmC,CAAC,EAAE;QAC7G,KAAK,CAAC,aAAa,GAAG,KAAK;QAC3B,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC;MACnC;IACF,CAAC;IACD,KAAK,GAAG;MACN,MAAM,EAAE,IAAI,cAAc,CAAC,MAAM;QAC/B,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa;QACtC,IACE,KAAK,EAAE,aAAa,KAAK,KAAK,IAC9B,OAAO,YAAY,WAAW,IAC9B,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IACtB,OAAO,CAAC,OAAO,CAAC,mCAAmC,CAAC,EAEpD,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,KAC1B,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC;MAC1B,CAAC,CAAC;MACF,KAAK;MACL,aAAa,EAAE;IACjB,CAAC;IACD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,oBAAoB,EAAE;MAAE,KAAK,EAAE,KAAK;MAAE,YAAY,EAAE;IAAK,CAAC,CAAC;IACvF,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC;EACzC;EACA,KAAK,CAAC,aAAa,GAAG,IAAI;EAC1B,MAAM;IAAE;EAAO,CAAC,GAAG,KAAK;EACxB,MAAM,CAAC,UAAU,CAAC,CAAC;EACnB;EACA,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;EACpB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;EACpE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC;EACnB,OAAO,KAAK;AACd;AACA,MAAM,gBAAgB,GAAG,IAAI,aAAa,2EAA2E,cAAc,IAAI,eAAe,GAAG;;AAEzJ;AACA;AACA;AACA;AACA,OAAO,SAAS,QAAQ,CAAC,KAAoB,EAAe;EAC1D,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC;EAChC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;EACtF,MAAM,UAAU,GAAG,IAAI,GAAG,CAAwB,CAAC;EACnD,IAAI,IAAkC;EACtC,gBAAgB,CACd,MAAM,IAAI,EACV,MAAM,IACR,CAAC;EACD,OAAO,CAAC,MAAM;IACZ,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,IAAI,IAAI,EAAE,cAAc,EAAE,eAAe,CAAC;IAC1E,IAAI,QAAQ,GAAG,KAAK;IACpB;IACA;IACA;IACA,cAAc,CAAC,MAAM;MACnB,IAAI,CAAC,QAAQ,IAAI,KAAK,EAAE,aAAa,EAAE,cAAc,CAAC,IAAI,IAAI,IAAI,EAAE,eAAe,CAAC;IACtF,CAAC,CAAC;IACF,SAAS,CAAC,MAAM;MACd,QAAQ,GAAG,IAAI;MACf,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;MAC1B,IAAI,IAAI,IAAI,KAAK,EAAE;QACjB,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC;QAChD,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,oBAAoB,CAAC;MACpD;IACF,CAAC,CAAC;EACJ,CAAC,CAAC;EACF;EACA,YAAY,CACV,EAAE,CACA,MAAM,KAAK,CAAC,OAAO,EACnB,MAAM,aAAa,CAAC,IAAI,IAAI,IAAI,EAAE,cAAc,EAAE,eAAe,CAAC,EAClE;IAAE,KAAK,EAAE;EAAK,CAChB,CACF,CAAC;EACD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC;EAC3C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,YAAY,CAAiB,EAAE,CAAC;EAClE,MAAM,YAAY,GAAI,IAAa,IAAK;IACtC,IAAI,IAAI,EAAE;MACR,YAAY,CACV,KAAK,CAAC,KAAK,CACR,MAAM,CACL,CAAC,IAAI,EAAE,KAAK,KACV,IAAI,CAAC,EAAE,KAAK,KAAK,CAAC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CACxH,CAAC,CACA,GAAG,CAAE,IAAI,KAAM;QACd,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,QAAQ,EAAE,IAAI,CAAC,KAAK,KAAK,SAAS,0BAAuC,IAAI,CAAC,KAAK,KAAW;MAChG,CAAC,CAAC,CACN,CAAC;IACH;IACA,OAAO,CAAC,IAAI,CAAC;EACf,CAAC;EACD,MAAM,UAAU,GAAI,IAAa,uBAE5B,IAAI;IAAA,IAAC,IAAI;MAAA,OAAE,IAAI,CAAC,IAAI;IAAA;IAAA,UAAI,IAAI,oDAAe,IAAI,CAAC,CAAC;EAAuB,4BACnB,IAAI,CAAC,KAAK,sBAC/D,IAAI;IAAA,IAAC,IAAI;MAAA,OAAE,IAAI,CAAC,KAAK,KAAK,SAAS;IAAA;IAAA;MAAA,8BACE,IAAI,CAAC,KAAK;IAAA;EAAA,GAGnD;EACD,sBACc,0BAAiB,KAAK,CAAC,KAAK,IAAI,EAAE,SAAE,wCAAc,KAAK,CAAC,KAAK,4CAEpE,GAAG;IAAA,IAAC,IAAI;MAAA,OAAE,MAAM,CAAC,CAAC,CAAC,IAAI;IAAA;IAAA,UACpB,KAAK,IAAK;MACV,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,KAAK,CAAE;MACtC,SAAS,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;MACzC,+DAImB,IAAI,CAAC,CAAC,CAAC,EAAE,wDACH,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,YAAG,SAAS,iDAEnC,IAAI,CAAC,CAAC,CAAC,IAAI,iDAAgB,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,OAAO,GAAG,MAAM,YAAG,SAAS,0BAC5G,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAI3B;EAAC,gCAEF,IAAI;IAAA,IAAC,IAAI;MAAA,OAAE,MAAM,CAAC,CAAC,CAAC,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC;IAAA;IAAA;MAAA,kDACf,MAAM,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,GAAG,EAAE,YAAG,SAAS,4CAChF,QAAQ,CAAC,IAAI;QAAA,IAAC,KAAK;UAAA,OAAE,SAAS,CAAC,CAAC;QAAA;QAAA,IAAE,IAAI;UAAA,OAAE,IAAI,CAAC,CAAC;QAAA;QAAE,YAAY,EAAE,YAAY;QAAA,IAAE,KAAK;UAAA,OAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;QAAA;QAAA;UAAA,yBACrG,QAAQ,CAAC,OAAO;YAAC,UAAU;YAAA;YAAA,IAAgE,KAAK;cAAA,OAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;YAAA;YAAA;cAAA,gCACjF,QAAQ,CAAC,CAAC,CAAC,IAAI,sBAClD,IAAI;gBAAA,IAAC,IAAI;kBAAA,OAAE,MAAM,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC;gBAAA;gBAAA;kBAAA,+BAE7B,MAAM,CAAC,CAAC,CAAC,UAAU;gBAAA;cAAA,sBAGxB,IAAI;gBAAA,IAAC,IAAI;kBAAA,OAAE,MAAM,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC;gBAAA;gBAAA;kBAAA,+BAEhC,MAAM,CAAC,CAAC,CAAC,aAAa;gBAAA;cAAA;YAAA;UAAA;QAAA;MAAA;IAAA;EAAA,KASnB,gBAAgB;AAGzC","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["actions/ViewTabs.tsx"],"sourcesContent":["import { createEffect, createMemo, createSignal, For, type JSX, on, onCleanup, onMount, Show } from \"solid-js\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { createScrollFade, scrollFadeEdges } from \"../layout/scroll-fade\";\nimport Dropdown, { type DropdownItem } from \"./Dropdown\";\n\nexport type ViewTab = {\n id: string;\n label: string;\n href: string;\n icon?: string;\n count?: number | string;\n /** A short quiet label after the count, such as \"New\" for a view people have not opened yet. */\n badge?: string;\n /** Stays in the strip after More at every width, for a view of another kind than the ones that overflow. */\n pinned?: boolean;\n};\nexport type ViewTabsProps = {\n /** Accessible name of the navigation landmark, e.g. \"Views\". */\n label: string;\n items: readonly ViewTab[];\n /** Id of the open view; the server reads it from `?view=`. Unknown ids select nothing. */\n current: string | undefined;\n /** At most this many views before More (default 5). */\n max?: number;\n /**\n * The strip shows only the views that fit its room and counts the rest in More, instead of scrolling them. The open\n * view and pinned views stay. Pair it with a larger `max` so a wide head shows more views.\n */\n fit?: boolean;\n class?: string;\n};\n\n/** Two deterministic strip levels; only opening the menu reads CSS visibility. */\nexport function viewTabsLayout(items: readonly ViewTab[], current: string | undefined, max = 5) {\n // At least one slot keeps the current view visible, including for an invalid limit.\n const limit = Number.isFinite(max) ? Math.max(1, Math.floor(max)) : 5;\n // Pinned views never overflow, so they take no slot.\n const flowing = items.flatMap((item, index) => (item.pinned ? [] : [index]));\n const pinned = items.flatMap((item, index) => (item.pinned ? [index] : []));\n const currentIndex = items.findIndex((item) => item.id === current);\n const currentPosition = flowing.indexOf(currentIndex);\n const visible = (size: number) => {\n const indices = flowing.slice(0, size);\n if (currentPosition >= size) indices[indices.length - 1] = currentIndex;\n return indices;\n };\n const wide = visible(limit);\n const compact = visible(Math.min(limit, 3));\n return { wide, compact, pinned, wideHidden: flowing.length - wide.length, compactHidden: flowing.length - compact.length };\n}\n\n/**\n * Self-contained: the server script embeds this before hydration. A fitting strip first keeps only the views that fit\n * its room and counts the rest in More; then the open or focused view scrolls into the strip.\n */\nfunction settleViewTabs(list: Element | null, edges: (element: HTMLElement) => string | undefined, focused?: HTMLElement) {\n if (!(list instanceof HTMLElement)) return;\n const more = list.querySelector<HTMLElement>(\":scope > .k2b-view-tabs__more\");\n const fitCount = more?.querySelector<HTMLElement>('.k2b-view-tabs__more-count[data-level=\"fit\"]');\n const flowing = Array.from(list.querySelectorAll<HTMLElement>(\":scope > .k2b-view-tabs__item:not([data-pinned])\"));\n\n for (const item of flowing) item.removeAttribute(\"data-fit-hidden\");\n more?.removeAttribute(\"data-fit\");\n // The shown entries against the strip's inner room, whatever its scroll and padding.\n const overflows = () => {\n const boxes = Array.from(list.children)\n .filter((child) => child instanceof HTMLElement && child.tagName === \"LI\" && child.getClientRects().length > 0)\n .map((child) => child.getBoundingClientRect());\n if (boxes.length === 0) return false;\n const style = getComputedStyle(list);\n const room = list.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);\n return Math.max(...boxes.map((box) => box.right)) - Math.min(...boxes.map((box) => box.left)) > room + 0.5;\n };\n // The phone head row keeps its scrolling strip: its CSS turns fitting off.\n const fits = list.hasAttribute(\"data-fit-room\") && getComputedStyle(list).getPropertyValue(\"--k2b-view-tabs-fit\").trim() !== \"none\";\n if (fits && more && fitCount && overflows()) {\n // Count from the level on screen: the compact strip already keeps fewer views than the wide one.\n const compact = more.querySelector<HTMLElement>('.k2b-view-tabs__more-count[data-level=\"compact\"]');\n const compactShown = Boolean(compact && getComputedStyle(compact).display !== \"none\");\n const behind = Number(compactShown ? more.dataset.behindCompact : more.dataset.behindWide) || 0;\n // The last views on screen leave first; the open view stays.\n const candidates = flowing.filter((item) => item.getClientRects().length > 0 && !item.querySelector(\"[aria-current=page]\")).reverse();\n more.setAttribute(\"data-fit\", \"\");\n let hidden = 0;\n for (const item of candidates) {\n item.setAttribute(\"data-fit-hidden\", \"\");\n hidden += 1;\n fitCount.textContent = `+${behind + hidden}`;\n if (!overflows()) break;\n }\n if (hidden === 0) more.removeAttribute(\"data-fit\");\n }\n const current = focused ?? list.querySelector(\".k2b-view-tabs__tab[aria-current=page]\");\n if (current && current.getClientRects().length > 0 && list.scrollWidth - list.clientWidth > 1) {\n const bounds = list.getBoundingClientRect();\n const item = current.getBoundingClientRect();\n const fade = edges(list);\n const style = getComputedStyle(list);\n const left = fade === \"top\" || fade === \"both\" ? parseFloat(style.scrollPaddingLeft) || 0 : 0;\n const right = fade === \"bottom\" || fade === \"both\" ? parseFloat(style.scrollPaddingRight) || 0 : 0;\n if (item.left < bounds.left + left || item.right > bounds.right - right) {\n let offset = item.left - bounds.left - (bounds.width - item.width) / 2;\n if (focused) {\n // Reserve padding at the destination edge; native clamping drops it when that end is reached.\n offset =\n item.left < bounds.left + left\n ? item.left - bounds.left - (parseFloat(style.scrollPaddingLeft) || 0)\n : item.right - bounds.right + (parseFloat(style.scrollPaddingRight) || 0);\n }\n // Only the strip moves: scrollIntoView would also scroll the page. Instant keeps the first frame free of animation.\n list.scrollTo({ left: list.scrollLeft + offset, behavior: \"instant\" });\n }\n }\n const fade = edges(list);\n if (fade) list.setAttribute(\"data-scroll-fade\", fade);\n else list.removeAttribute(\"data-scroll-fade\");\n}\n\ntype WatchedViewTabs = HTMLElement & {\n __k2bViewTabsWatch?: {\n resize: ResizeObserver;\n focus: (event: FocusEvent) => void;\n preferCurrent: boolean;\n connect: () => void;\n /** Runs one fit in the next frame; calls before it runs share it. */\n schedule: () => void;\n frame: number;\n /** The head lead's width and the widths of the shown entries when the last fit ended. */\n room: string;\n /** Fits again once web fonts finish loading: their metrics change every view's width, not the lead's. */\n fonts: () => void;\n };\n};\n\n/**\n * Also embedded, so it must not close over module state.\n *\n * A fitting strip converges by construction. It fits again only when the strip renders, web fonts finish loading, or\n * a notification finds another width of the head lead or of an entry that the last fit left shown. A fit only shows\n * and hides entries: the lead is an inline-size container that takes no width from the strip, and every entry keeps\n * its own width (`flex: none`), so the notifications a fit causes find the widths it ended with and stop there.\n */\nfunction watchViewTabs(list: WatchedViewTabs | null, settle: typeof settleViewTabs, edges: (element: HTMLElement) => string | undefined) {\n if (!(list instanceof HTMLElement)) return;\n const fits = list.hasAttribute(\"data-fit-room\");\n const roomOf = () => list.closest<HTMLElement>(\".k2b-app-workspace__head-lead\");\n // What a fit depends on: the lead's width and each shown entry's width, which fonts, icons and labels change.\n const measured = () =>\n [roomOf()?.clientWidth ?? -1, ...Array.from(list.children, (child) => child.getBoundingClientRect().width)].join(\" \");\n // A DOM property survives hydration: one observer and focus listener per reused list.\n let watch = list.__k2bViewTabsWatch;\n if (!watch) {\n const focus = (event: FocusEvent) => {\n // Pointer focus must not move the target under the pointer.\n if (watch && event.target instanceof HTMLElement && event.target.matches(\".k2b-view-tabs__tab:focus-visible\")) {\n watch.preferCurrent = false;\n settle(list, edges, event.target);\n }\n };\n const settleNow = () => {\n const focused = document.activeElement;\n if (\n watch?.preferCurrent === false &&\n focused instanceof HTMLElement &&\n list.contains(focused) &&\n focused.matches(\".k2b-view-tabs__tab:focus-visible\")\n )\n settle(list, edges, focused);\n else settle(list, edges);\n };\n // Head actions parse after the views; settle again after their layout, before paint, and on later resizes.\n const connect = () => {\n if (!watch) return;\n watch.resize.disconnect();\n watch.resize.observe(list);\n for (const child of Array.from(list.children)) watch.resize.observe(child);\n const room = roomOf();\n if (fits && room) watch.resize.observe(room);\n };\n // Fitting resizes the strip and possibly the head, which other observers may watch. Inside a ResizeObserver\n // callback that would leave their notifications undelivered, so a fit runs in the next frame, still before it paints.\n const schedule = () => {\n if (!watch || watch.frame) return;\n watch.frame = requestAnimationFrame(() => {\n if (!watch) return;\n watch.frame = 0;\n if (!list.isConnected) return;\n settleNow();\n watch.room = measured();\n });\n };\n watch = {\n resize: new ResizeObserver(() => {\n // A scrolling strip only scrolls and settles at once.\n if (!fits) return settleNow();\n // Heights follow the fit, and hidden entries measure 0 by its own doing: only another measure counts.\n if (watch && measured() !== watch.room) watch.schedule();\n }),\n focus,\n preferCurrent: true,\n connect,\n schedule,\n frame: 0,\n room: \"\",\n fonts: () => watch?.schedule(),\n };\n Object.defineProperty(list, \"__k2bViewTabsWatch\", { value: watch, configurable: true });\n list.addEventListener(\"focusin\", focus);\n // A first visit can paint before the web fonts load; the server frame and the client then fit the same metrics.\n if (fits && document.fonts) {\n document.fonts.addEventListener(\"loadingdone\", watch.fonts);\n void document.fonts.ready.then(watch.fonts);\n }\n }\n watch.preferCurrent = true;\n watch.connect();\n settle(list, edges);\n if (fits) {\n watch.room = measured();\n // The head's actions parse after the views. A frame's animation callbacks run before its first layout and\n // paint, so this fit also counts their room in the first frame.\n watch.schedule();\n }\n return watch;\n}\nconst VIEW_TABS_SCRIPT = `(${watchViewTabs})(document.currentScript&&document.currentScript.previousElementSibling,${settleViewTabs},${scrollFadeEdges})`;\n\n/**\n * URL-backed object views. Every view is an ordinary document link, in the strip and under More, so reload, back,\n * and forward keep the view the URL names.\n */\nexport function ViewTabs(props: ViewTabsProps): JSX.Element {\n const messages = useUiMessages();\n const layout = createMemo(() => viewTabsLayout(props.items, props.current, props.max));\n const stripItems = new Map<number, HTMLLIElement>();\n let list: HTMLUListElement | undefined;\n createScrollFade(\n () => list,\n () => true,\n );\n onMount(() => {\n const watch = watchViewTabs(list ?? null, settleViewTabs, scrollFadeEdges);\n let disposed = false;\n // Later mount effects can invalidate WebKit's scroll layout after this\n // initial settle. Finish once the mount task is complete, before its first\n // frame; ResizeObserver alone runs too late for that frame.\n queueMicrotask(() => {\n if (!disposed && watch?.preferCurrent) settleViewTabs(list ?? null, scrollFadeEdges);\n });\n onCleanup(() => {\n disposed = true;\n watch?.resize.disconnect();\n if (watch?.frame) cancelAnimationFrame(watch.frame);\n if (watch) document.fonts?.removeEventListener(\"loadingdone\", watch.fonts);\n if (list && watch) {\n list.removeEventListener(\"focusin\", watch.focus);\n Reflect.deleteProperty(list, \"__k2bViewTabsWatch\");\n }\n });\n });\n // User effects run after Solid's render effects, including For and aria-current, in the same task.\n // What the strip shows: another open view, view, label, count or badge can change what fits. Equal content in a new\n // array settles nothing again.\n const shown = createMemo(() =>\n [\n props.current ?? \"\",\n layout().wide.join(\",\"),\n ...props.items.map((item) => `${item.id}\\u0000${item.label}\\u0000${item.count ?? \"\"}\\u0000${item.badge ?? \"\"}`),\n ].join(\"\\u0001\"),\n );\n createEffect(on(shown, () => watchViewTabs(list ?? null, settleViewTabs, scrollFadeEdges), { defer: true }));\n const [open, setOpen] = createSignal(false);\n const [menuItems, setMenuItems] = createSignal<DropdownItem[]>([]);\n const onOpenChange = (next: boolean) => {\n if (next) {\n setMenuItems(\n props.items\n .filter(\n (item, index) =>\n !item.pinned &&\n item.id !== props.current &&\n (!layout().wide.includes(index) || stripItems.get(index)?.getClientRects().length === 0),\n )\n .map((item) => ({\n href: item.href,\n label: item.label,\n icon: item.icon,\n trailing: item.count !== undefined ? <span class=\"k2b-view-tabs__count\">{item.count}</span> : undefined,\n })),\n );\n }\n setOpen(next);\n };\n const tabContent = (item: ViewTab) => (\n <>\n <Show when={item.icon}>{(icon) => <i class={icon()} aria-hidden=\"true\" />}</Show>\n <span class=\"k2b-view-tabs__label k2b-truncate-fade\">{item.label}</span>\n <Show when={item.count !== undefined}>\n <span class=\"k2b-view-tabs__count\">{item.count}</span>\n </Show>\n <Show when={item.badge}>{(badge) => <span class=\"k2b-view-tabs__badge\">{badge()}</span>}</Show>\n </>\n );\n const stripItem = (index: number) => {\n const item = () => props.items[index]!;\n onCleanup(() => stripItems.delete(index));\n return (\n <li\n ref={(element) => stripItems.set(index, element)}\n class=\"k2b-view-tabs__item\"\n data-view-tab={item().id}\n data-pinned={item().pinned ? \"\" : undefined}\n data-compact-hidden={!item().pinned && !layout().compact.includes(index) ? \"\" : undefined}\n >\n <a class=\"k2b-view-tabs__tab\" href={item().href} aria-current={item().id === props.current ? \"page\" : undefined}>\n {tabContent(item())}\n </a>\n </li>\n );\n };\n return (\n <nav class={`k2b-view-tabs ${props.class ?? \"\"}`} aria-label={props.label}>\n <ul\n ref={list}\n class=\"k2b-view-tabs__list\"\n data-scroll-fade-mode=\"both\"\n data-scroll-fade-axis=\"horizontal\"\n data-fit-room={props.fit ? \"\" : undefined}\n >\n <For each={layout().wide}>{stripItem}</For>\n {/* A fitting strip keeps More ready, hidden until a view leaves the strip. */}\n <Show when={layout().compactHidden + layout().wideHidden > 0 || (props.fit && layout().wide.length > 1)}>\n <li\n class=\"k2b-view-tabs__more\"\n data-wide={layout().wideHidden > 0 ? \"\" : undefined}\n data-behind-wide={layout().wideHidden}\n data-behind-compact={layout().compactHidden}\n >\n <Dropdown.Root items={menuItems()} open={open()} onOpenChange={onOpenChange} label={messages().moreViews}>\n <Dropdown.Trigger appearance=\"plain\" class=\"k2b-view-tabs__tab k2b-view-tabs__more-trigger\" label={messages().moreViews}>\n <span class=\"k2b-view-tabs__label\">{messages().more}</span>\n <Show when={layout().wideHidden > 0}>\n <span class=\"k2b-view-tabs__more-count\" data-level=\"wide\">\n +{layout().wideHidden}\n </span>\n </Show>\n <Show when={layout().compactHidden > 0}>\n <span class=\"k2b-view-tabs__more-count\" data-level=\"compact\">\n +{layout().compactHidden}\n </span>\n </Show>\n {/* Written by the strip's fit, never by the render, so a rerender keeps the count it measured. */}\n <span class=\"k2b-view-tabs__more-count\" data-level=\"fit\" />\n <i class=\"ti ti-chevron-down\" aria-hidden=\"true\" />\n </Dropdown.Trigger>\n </Dropdown.Root>\n </li>\n </Show>\n <For each={layout().pinned}>{stripItem}</For>\n </ul>\n <script innerHTML={VIEW_TABS_SCRIPT} />\n </nav>\n );\n}\n"],"mappings":"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","ignoreList":[]}
@@ -12,9 +12,8 @@ import BottomSheet, { bottomSheetOptions } from "../layout/BottomSheet";
12
12
  import { focusMenuRow, focusRowSegment } from "./menu-rows";
13
13
  import { filterDropdownItems, MenuSearch } from "./menu-search";
14
14
 
15
- /** Phones, where a menu opens as a bottom sheet instead of beside its trigger. */
16
- export const PHONE_SHEET_QUERY = "(max-width: 47.999rem)";
17
- export const prefersMenuSheet = () => typeof matchMedia === "function" && matchMedia(PHONE_SHEET_QUERY).matches;
15
+ // Kept apart so a control can check for phones without loading the sheet and its dialog imports.
16
+ export { PHONE_SHEET_QUERY, prefersMenuSheet } from "./phone-sheet";
18
17
  /**
19
18
  * Follows a link the way a click on it would, so a page's own link handling, such as progressive navigation, still
20
19
  * applies; it runs only after the sheet has left the history.
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["actions/menu-sheet.tsx"],"sourcesContent":["import { createSignal, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { dialogCore } from \"../feedback/dialog-core\";\nimport { LocaleProvider } from \"../intl/locale\";\nimport BottomSheet, { bottomSheetOptions } from \"../layout/BottomSheet\";\nimport type { DropdownAction, DropdownChoice, DropdownItem, DropdownSearch } from \"./Dropdown\";\nimport { focusMenuRow, focusRowSegment } from \"./menu-rows\";\nimport { filterDropdownItems, MenuSearch } from \"./menu-search\";\n\n/** Phones, where a menu opens as a bottom sheet instead of beside its trigger. */\nexport const PHONE_SHEET_QUERY = \"(max-width: 47.999rem)\";\n\nexport const prefersMenuSheet = (): boolean => typeof matchMedia === \"function\" && matchMedia(PHONE_SHEET_QUERY).matches;\n\ntype SheetEntry = DropdownAction | DropdownChoice;\ntype SheetItem = SheetEntry | { items: readonly SheetEntry[] };\n\n/**\n * Follows a link the way a click on it would, so a page's own link handling, such as progressive navigation, still\n * applies; it runs only after the sheet has left the history.\n */\nconst followLink = (href: string) => {\n const link = document.createElement(\"a\");\n link.href = href;\n link.hidden = true;\n document.body.append(link);\n link.click();\n link.remove();\n};\n\n/**\n * The sheet's items hand their action to `run` instead of running it while the sheet closes: `dialogCore` settles the\n * sheet after it left the history, and only then may an action navigate or open the next dialog.\n * A choice or action that keeps the menu open, or an `immediate` action, runs at once.\n */\nconst afterClose = <T extends DropdownItem>(items: readonly T[], run: (action: () => void) => void): T[] => {\n const entry = (item: SheetEntry): SheetEntry => {\n // A plain link would navigate while the sheet's history entry is still being removed, and that Back could\n // undo the navigation; a plain click instead follows it once the sheet is gone. Modified clicks and external\n // links keep the browser's own behavior.\n if (!item.action && \"href\" in item && item.href && !(\"external\" in item && item.external)) {\n const href = item.href;\n return { ...item, action: () => run(() => followLink(href)) } as SheetEntry;\n }\n const action = item.action;\n if (!action || (\"closeOnSelect\" in item && item.closeOnSelect === false) || (\"immediate\" in item && item.immediate)) return item;\n return { ...item, action: () => run(action) } as SheetEntry;\n };\n return items.map((item) => {\n const sheetItem = item as SheetItem;\n return (\"items\" in sheetItem ? { ...sheetItem, items: sheetItem.items.map(entry) } : entry(sheetItem)) as T;\n });\n};\n\nconst menuItems = (menu: HTMLElement): HTMLElement[] =>\n Array.from(menu.querySelectorAll<HTMLElement>(\"[role^='menuitem']:not([aria-disabled='true'])\"));\n\n/**\n * The menu inside the sheet: the same items, at touch size, with arrow keys for a connected keyboard. Focus starts on\n * the sheet itself, which shows no ring on a phone; the first arrow key moves it into the menu.\n */\nfunction SheetMenu(props: {\n Items: MenuSheetOptions[\"Items\"];\n items: readonly DropdownItem[];\n label: string;\n close: () => void;\n dialog: HTMLDialogElement;\n icons?: \"plain\" | \"tile\";\n}): JSX.Element {\n let menu!: HTMLDivElement;\n const onKeyDown = (event: KeyboardEvent) => {\n const items = menuItems(menu);\n if (!focusMenuRow(items, event.key) && !focusRowSegment(items, event.key)) return;\n event.preventDefault();\n };\n // Keys pressed on the sheet itself, while it is the top level of the shared dialog.\n const onSheetKeyDown = (event: KeyboardEvent) => {\n if (event.target === props.dialog && props.dialog.lastElementChild?.contains(menu)) onKeyDown(event);\n };\n onMount(() => {\n props.dialog.addEventListener(\"keydown\", onSheetKeyDown);\n onCleanup(() => props.dialog.removeEventListener(\"keydown\", onSheetKeyDown));\n });\n return (\n <div\n ref={menu}\n class=\"k2b-gesture-menu__sheet-menu\"\n role=\"menu\"\n aria-label={props.label}\n data-icons={props.icons === \"tile\" ? \"tile\" : undefined}\n onKeyDown={onKeyDown}\n >\n <props.Items items={props.items} close={() => props.close()} />\n </div>\n );\n}\n\nexport type MenuSheetOptions = {\n /** Renders the rows: the dropdown's own `DropdownItems`, passed in so this module does not import it back. */\n Items: (props: { items: readonly DropdownItem[]; close: (restoreFocus?: boolean) => void }) => JSX.Element;\n items: readonly DropdownItem[];\n /** Names the sheet and its menu. */\n label: string;\n /** The render locale of the opener; the sheet renders outside its tree. */\n locale: string;\n /** Shown above the menu, for example a row of quick reactions. `close` closes the sheet. */\n top?: (close: () => void) => JSX.Element;\n search?: DropdownSearch;\n icons?: \"plain\" | \"tile\";\n /** A quiet sentence below the entries. */\n note?: string;\n signal?: AbortSignal;\n};\n\n/** A searchable menu in the sheet: the sheet's first field, so it takes the focus as the sheet opens, as in the menu. */\nfunction SheetSearch(props: {\n Items: MenuSheetOptions[\"Items\"];\n items: readonly DropdownItem[];\n search: DropdownSearch;\n wrap: <T extends DropdownItem>(items: readonly T[]) => T[];\n label: string;\n locale: string;\n close: () => void;\n icons?: \"plain\" | \"tile\";\n}): JSX.Element {\n const [query, setQuery] = createSignal(\"\");\n const filter = props.search.filter;\n return (\n <div\n class=\"k2b-gesture-menu__sheet-menu\"\n role=\"dialog\"\n aria-label={props.label}\n data-search=\"\"\n data-icons={props.icons === \"tile\" ? \"tile\" : undefined}\n >\n <MenuSearch\n Items={props.Items}\n items={props.items}\n search={{\n ...props.search,\n // The sheet's own items are wrapped already; a host's filter returns its unwrapped entries.\n filter: (value) => (filter ? props.wrap(filter(value)) : filterDropdownItems(props.items, value, props.locale)),\n }}\n label={props.label}\n query={query}\n setQuery={setQuery}\n close={() => props.close()}\n />\n </div>\n );\n}\n\n/** Opens menu items as a bottom sheet; resolves after the sheet closed and the chosen action ran. */\nexport const openMenuSheet = (options: MenuSheetOptions): Promise<void> => {\n let chosen: (() => void) | undefined;\n const choose = (action: () => void) => {\n chosen = action;\n };\n return dialogCore\n .open<void>(\n (close, context) => (\n <LocaleProvider locale={options.locale}>\n <BottomSheet onDismiss={context.requestDismiss}>\n <BottomSheet.Body>\n {options.top?.(() => close())}\n <Show\n when={options.search}\n fallback={\n <SheetMenu\n Items={options.Items}\n items={afterClose(options.items, choose)}\n label={options.label}\n close={() => close()}\n dialog={context.dialog}\n icons={options.icons}\n />\n }\n >\n {(search) => (\n <SheetSearch\n Items={options.Items}\n items={afterClose(options.items, choose)}\n search={search()}\n wrap={(items) => afterClose(items, choose)}\n label={options.label}\n locale={options.locale}\n close={() => close()}\n icons={options.icons}\n />\n )}\n </Show>\n <Show when={options.note}>\n <p class=\"k2b-dropdown__note\">{options.note}</p>\n </Show>\n </BottomSheet.Body>\n </BottomSheet>\n </LocaleProvider>\n ),\n { ...bottomSheetOptions, ariaLabel: options.label, history: true, signal: options.signal },\n )\n .then(() => chosen?.());\n};\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["actions/menu-sheet.tsx"],"sourcesContent":["import { createSignal, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { dialogCore } from \"../feedback/dialog-core\";\nimport { LocaleProvider } from \"../intl/locale\";\nimport BottomSheet, { bottomSheetOptions } from \"../layout/BottomSheet\";\nimport type { DropdownAction, DropdownChoice, DropdownItem, DropdownSearch } from \"./Dropdown\";\nimport { focusMenuRow, focusRowSegment } from \"./menu-rows\";\nimport { filterDropdownItems, MenuSearch } from \"./menu-search\";\n\n// Kept apart so a control can check for phones without loading the sheet and its dialog imports.\nexport { PHONE_SHEET_QUERY, prefersMenuSheet } from \"./phone-sheet\";\n\ntype SheetEntry = DropdownAction | DropdownChoice;\ntype SheetItem = SheetEntry | { items: readonly SheetEntry[] };\n\n/**\n * Follows a link the way a click on it would, so a page's own link handling, such as progressive navigation, still\n * applies; it runs only after the sheet has left the history.\n */\nconst followLink = (href: string) => {\n const link = document.createElement(\"a\");\n link.href = href;\n link.hidden = true;\n document.body.append(link);\n link.click();\n link.remove();\n};\n\n/**\n * The sheet's items hand their action to `run` instead of running it while the sheet closes: `dialogCore` settles the\n * sheet after it left the history, and only then may an action navigate or open the next dialog.\n * A choice or action that keeps the menu open, or an `immediate` action, runs at once.\n */\nconst afterClose = <T extends DropdownItem>(items: readonly T[], run: (action: () => void) => void): T[] => {\n const entry = (item: SheetEntry): SheetEntry => {\n // A plain link would navigate while the sheet's history entry is still being removed, and that Back could\n // undo the navigation; a plain click instead follows it once the sheet is gone. Modified clicks and external\n // links keep the browser's own behavior.\n if (!item.action && \"href\" in item && item.href && !(\"external\" in item && item.external)) {\n const href = item.href;\n return { ...item, action: () => run(() => followLink(href)) } as SheetEntry;\n }\n const action = item.action;\n if (!action || (\"closeOnSelect\" in item && item.closeOnSelect === false) || (\"immediate\" in item && item.immediate)) return item;\n return { ...item, action: () => run(action) } as SheetEntry;\n };\n return items.map((item) => {\n const sheetItem = item as SheetItem;\n return (\"items\" in sheetItem ? { ...sheetItem, items: sheetItem.items.map(entry) } : entry(sheetItem)) as T;\n });\n};\n\nconst menuItems = (menu: HTMLElement): HTMLElement[] =>\n Array.from(menu.querySelectorAll<HTMLElement>(\"[role^='menuitem']:not([aria-disabled='true'])\"));\n\n/**\n * The menu inside the sheet: the same items, at touch size, with arrow keys for a connected keyboard. Focus starts on\n * the sheet itself, which shows no ring on a phone; the first arrow key moves it into the menu.\n */\nfunction SheetMenu(props: {\n Items: MenuSheetOptions[\"Items\"];\n items: readonly DropdownItem[];\n label: string;\n close: () => void;\n dialog: HTMLDialogElement;\n icons?: \"plain\" | \"tile\";\n}): JSX.Element {\n let menu!: HTMLDivElement;\n const onKeyDown = (event: KeyboardEvent) => {\n const items = menuItems(menu);\n if (!focusMenuRow(items, event.key) && !focusRowSegment(items, event.key)) return;\n event.preventDefault();\n };\n // Keys pressed on the sheet itself, while it is the top level of the shared dialog.\n const onSheetKeyDown = (event: KeyboardEvent) => {\n if (event.target === props.dialog && props.dialog.lastElementChild?.contains(menu)) onKeyDown(event);\n };\n onMount(() => {\n props.dialog.addEventListener(\"keydown\", onSheetKeyDown);\n onCleanup(() => props.dialog.removeEventListener(\"keydown\", onSheetKeyDown));\n });\n return (\n <div\n ref={menu}\n class=\"k2b-gesture-menu__sheet-menu\"\n role=\"menu\"\n aria-label={props.label}\n data-icons={props.icons === \"tile\" ? \"tile\" : undefined}\n onKeyDown={onKeyDown}\n >\n <props.Items items={props.items} close={() => props.close()} />\n </div>\n );\n}\n\nexport type MenuSheetOptions = {\n /** Renders the rows: the dropdown's own `DropdownItems`, passed in so this module does not import it back. */\n Items: (props: { items: readonly DropdownItem[]; close: (restoreFocus?: boolean) => void }) => JSX.Element;\n items: readonly DropdownItem[];\n /** Names the sheet and its menu. */\n label: string;\n /** The render locale of the opener; the sheet renders outside its tree. */\n locale: string;\n /** Shown above the menu, for example a row of quick reactions. `close` closes the sheet. */\n top?: (close: () => void) => JSX.Element;\n search?: DropdownSearch;\n icons?: \"plain\" | \"tile\";\n /** A quiet sentence below the entries. */\n note?: string;\n signal?: AbortSignal;\n};\n\n/** A searchable menu in the sheet: the sheet's first field, so it takes the focus as the sheet opens, as in the menu. */\nfunction SheetSearch(props: {\n Items: MenuSheetOptions[\"Items\"];\n items: readonly DropdownItem[];\n search: DropdownSearch;\n wrap: <T extends DropdownItem>(items: readonly T[]) => T[];\n label: string;\n locale: string;\n close: () => void;\n icons?: \"plain\" | \"tile\";\n}): JSX.Element {\n const [query, setQuery] = createSignal(\"\");\n const filter = props.search.filter;\n return (\n <div\n class=\"k2b-gesture-menu__sheet-menu\"\n role=\"dialog\"\n aria-label={props.label}\n data-search=\"\"\n data-icons={props.icons === \"tile\" ? \"tile\" : undefined}\n >\n <MenuSearch\n Items={props.Items}\n items={props.items}\n search={{\n ...props.search,\n // The sheet's own items are wrapped already; a host's filter returns its unwrapped entries.\n filter: (value) => (filter ? props.wrap(filter(value)) : filterDropdownItems(props.items, value, props.locale)),\n }}\n label={props.label}\n query={query}\n setQuery={setQuery}\n close={() => props.close()}\n />\n </div>\n );\n}\n\n/** Opens menu items as a bottom sheet; resolves after the sheet closed and the chosen action ran. */\nexport const openMenuSheet = (options: MenuSheetOptions): Promise<void> => {\n let chosen: (() => void) | undefined;\n const choose = (action: () => void) => {\n chosen = action;\n };\n return dialogCore\n .open<void>(\n (close, context) => (\n <LocaleProvider locale={options.locale}>\n <BottomSheet onDismiss={context.requestDismiss}>\n <BottomSheet.Body>\n {options.top?.(() => close())}\n <Show\n when={options.search}\n fallback={\n <SheetMenu\n Items={options.Items}\n items={afterClose(options.items, choose)}\n label={options.label}\n close={() => close()}\n dialog={context.dialog}\n icons={options.icons}\n />\n }\n >\n {(search) => (\n <SheetSearch\n Items={options.Items}\n items={afterClose(options.items, choose)}\n search={search()}\n wrap={(items) => afterClose(items, choose)}\n label={options.label}\n locale={options.locale}\n close={() => close()}\n icons={options.icons}\n />\n )}\n </Show>\n <Show when={options.note}>\n <p class=\"k2b-dropdown__note\">{options.note}</p>\n </Show>\n </BottomSheet.Body>\n </BottomSheet>\n </LocaleProvider>\n ),\n { ...bottomSheetOptions, ariaLabel: options.label, history: true, signal: options.signal },\n )\n .then(() => chosen?.());\n};\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,4 @@
1
+ /** Phones, where a menu or a date calendar opens as a bottom sheet instead of beside its trigger. */
2
+ export const PHONE_SHEET_QUERY = "(max-width: 47.999rem)";
3
+ export const prefersMenuSheet = () => typeof matchMedia === "function" && matchMedia(PHONE_SHEET_QUERY).matches;
4
+ //# sourceMappingURL=phone-sheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["actions/phone-sheet.ts"],"sourcesContent":["/** Phones, where a menu or a date calendar opens as a bottom sheet instead of beside its trigger. */\nexport const PHONE_SHEET_QUERY = \"(max-width: 47.999rem)\";\n\nexport const prefersMenuSheet = (): boolean => typeof matchMedia === \"function\" && matchMedia(PHONE_SHEET_QUERY).matches;\n"],"mappings":"AAAA;AACA,OAAO,MAAM,iBAAiB,GAAG,wBAAwB;AAEzD,OAAO,MAAM,gBAAgB,GAAG,MAAe,OAAO,UAAU,KAAK,UAAU,IAAI,UAAU,CAAC,iBAAiB,CAAC,CAAC,OAAO","ignoreList":[]}
@@ -36,7 +36,6 @@ import { PanelHeader } from "../layout/PanelHeader";
36
36
  import { Paper } from "../surfaces/Paper";
37
37
  import Placeholder from "../surfaces/Placeholder";
38
38
  import { useDataTableMessages } from "./data-table-messages";
39
- const WIDE_TABLE_COLUMNS = 8;
40
39
  const DataTablePanelContext = createContext();
41
40
  const defaultRender = (value, locale, messages) => {
42
41
  if (value === null || value === undefined || value === "") return "—";
@@ -387,7 +386,7 @@ function DataTableRoot(props) {
387
386
  },
388
387
  onMouseLeave: () => setHoveredColumn(null),
389
388
  get children() {
390
- return [_$ssr(_tmpl$0, _$ssrAttribute("aria-label", labelledBy() ? _$escape(undefined, true) : _$escape(props.ariaLabel ?? dataTableMessages().dataTable, true), false) + _$ssrAttribute("aria-labelledby", _$escape(labelledBy(), true), false) + _$ssrAttribute("aria-busy", props.loading ? "true" : _$escape(undefined, true), false), `k2b-table-wrap${props.scrollClass ? ` ${_$escape(props.scrollClass, true)}` : ""}`, _$ssrAttribute("data-density", props.density === "compact" ? "compact" : _$escape(undefined, true), false) + _$ssrAttribute("data-has-footer", props.footer ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-surface", _$escape(surface(), true), false) + _$ssrAttribute("data-scroll-preserve", _$escape(props.scrollPreserveKey || undefined, true), false), _$ssrAttribute("class", _$escape(tableClass(), true), false) + _$ssrAttribute("data-fill", props.fillHeight ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-wide", props.columns.length > WIDE_TABLE_COLUMNS ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
389
+ return [_$ssr(_tmpl$0, _$ssrAttribute("aria-label", labelledBy() ? _$escape(undefined, true) : _$escape(props.ariaLabel ?? dataTableMessages().dataTable, true), false) + _$ssrAttribute("aria-labelledby", _$escape(labelledBy(), true), false) + _$ssrAttribute("aria-busy", props.loading ? "true" : _$escape(undefined, true), false), `k2b-table-wrap${props.scrollClass ? ` ${_$escape(props.scrollClass, true)}` : ""}`, _$ssrAttribute("data-density", props.density === "compact" ? "compact" : _$escape(undefined, true), false) + _$ssrAttribute("data-has-footer", props.footer ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-surface", _$escape(surface(), true), false) + _$ssrAttribute("data-scroll-preserve", _$escape(props.scrollPreserveKey || undefined, true), false), _$ssrAttribute("class", _$escape(tableClass(), true), false) + _$ssrAttribute("data-fill", props.fillHeight ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
391
390
  get when() {
392
391
  return props.loading;
393
392
  },