@k2b/ui 0.45.0 → 0.47.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 (121) 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/ViewTabs.js +225 -93
  6. package/dist/browser/actions/ViewTabs.js.map +1 -1
  7. package/dist/browser/chat/ChatDecision.js +10 -6
  8. package/dist/browser/chat/ChatDecision.js.map +1 -1
  9. package/dist/browser/content/FileGrid.js +125 -99
  10. package/dist/browser/content/FileGrid.js.map +1 -1
  11. package/dist/browser/content/VirtualFeed.js +26 -21
  12. package/dist/browser/content/VirtualFeed.js.map +1 -1
  13. package/dist/browser/inputs/Combobox.js +14 -6
  14. package/dist/browser/inputs/Combobox.js.map +1 -1
  15. package/dist/browser/inputs/choice.js +36 -9
  16. package/dist/browser/inputs/choice.js.map +1 -1
  17. package/dist/browser/layout/AnchoredPopover.js +2 -1
  18. package/dist/browser/layout/AnchoredPopover.js.map +1 -1
  19. package/dist/browser/layout/AppWorkspace.js +348 -444
  20. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  21. package/dist/browser/layout/AppWorkspaceSidebarItemStatus.js +113 -0
  22. package/dist/browser/layout/AppWorkspaceSidebarItemStatus.js.map +1 -0
  23. package/dist/browser/layout/DetailPanel.js +130 -108
  24. package/dist/browser/layout/DetailPanel.js.map +1 -1
  25. package/dist/browser/layout/DetailSections.js +1123 -0
  26. package/dist/browser/layout/DetailSections.js.map +1 -0
  27. package/dist/browser/layout/DetailsToggle.js +32 -0
  28. package/dist/browser/layout/DetailsToggle.js.map +1 -0
  29. package/dist/browser/layout/Discussion.js +3 -0
  30. package/dist/browser/layout/Discussion.js.map +1 -1
  31. package/dist/browser/layout/MobileShell.js +141 -102
  32. package/dist/browser/layout/MobileShell.js.map +1 -1
  33. package/dist/browser/layout/PageTitle.js +33 -10
  34. package/dist/browser/layout/PageTitle.js.map +1 -1
  35. package/dist/browser/layout/app-workspace-controller.js +12 -0
  36. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  37. package/dist/browser/layout/detail-sections-messages.js +127 -0
  38. package/dist/browser/layout/detail-sections-messages.js.map +1 -0
  39. package/dist/browser/layout/index.js +3 -0
  40. package/dist/browser/layout/index.js.map +1 -1
  41. package/dist/browser/surfaces/Avatar.js +3 -1
  42. package/dist/browser/surfaces/Avatar.js.map +1 -1
  43. package/dist/browser/surfaces/AvatarStack.js +95 -61
  44. package/dist/browser/surfaces/AvatarStack.js.map +1 -1
  45. package/dist/browser/surfaces/ChangeList.js +77 -0
  46. package/dist/browser/surfaces/ChangeList.js.map +1 -0
  47. package/dist/browser/surfaces/index.js +2 -1
  48. package/dist/browser/surfaces/index.js.map +1 -1
  49. package/dist/browser/text-editor/TextEditor.js +19 -8
  50. package/dist/browser/text-editor/TextEditor.js.map +1 -1
  51. package/dist/ssr/actions/Dropdown.js +6 -0
  52. package/dist/ssr/actions/Dropdown.js.map +1 -1
  53. package/dist/ssr/actions/FilterChip.js +1 -1
  54. package/dist/ssr/actions/FilterChip.js.map +1 -1
  55. package/dist/ssr/actions/ViewTabs.js +144 -37
  56. package/dist/ssr/actions/ViewTabs.js.map +1 -1
  57. package/dist/ssr/chat/ChatDecision.js +10 -6
  58. package/dist/ssr/chat/ChatDecision.js.map +1 -1
  59. package/dist/ssr/content/FileGrid.js +36 -22
  60. package/dist/ssr/content/FileGrid.js.map +1 -1
  61. package/dist/ssr/content/VirtualFeed.js +26 -21
  62. package/dist/ssr/content/VirtualFeed.js.map +1 -1
  63. package/dist/ssr/inputs/Combobox.js +6 -3
  64. package/dist/ssr/inputs/Combobox.js.map +1 -1
  65. package/dist/ssr/inputs/choice.js +36 -9
  66. package/dist/ssr/inputs/choice.js.map +1 -1
  67. package/dist/ssr/layout/AnchoredPopover.js +2 -1
  68. package/dist/ssr/layout/AnchoredPopover.js.map +1 -1
  69. package/dist/ssr/layout/AppWorkspace.js +66 -132
  70. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  71. package/dist/ssr/layout/AppWorkspaceSidebarItemStatus.js +76 -0
  72. package/dist/ssr/layout/AppWorkspaceSidebarItemStatus.js.map +1 -0
  73. package/dist/ssr/layout/DetailPanel.js +18 -1
  74. package/dist/ssr/layout/DetailPanel.js.map +1 -1
  75. package/dist/ssr/layout/DetailSections.js +838 -0
  76. package/dist/ssr/layout/DetailSections.js.map +1 -0
  77. package/dist/ssr/layout/DetailsToggle.js +29 -0
  78. package/dist/ssr/layout/DetailsToggle.js.map +1 -0
  79. package/dist/ssr/layout/Discussion.js +3 -0
  80. package/dist/ssr/layout/Discussion.js.map +1 -1
  81. package/dist/ssr/layout/MobileShell.js +60 -29
  82. package/dist/ssr/layout/MobileShell.js.map +1 -1
  83. package/dist/ssr/layout/PageTitle.js +29 -6
  84. package/dist/ssr/layout/PageTitle.js.map +1 -1
  85. package/dist/ssr/layout/app-workspace-controller.js +12 -0
  86. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  87. package/dist/ssr/layout/detail-sections-messages.js +127 -0
  88. package/dist/ssr/layout/detail-sections-messages.js.map +1 -0
  89. package/dist/ssr/layout/index.js +3 -0
  90. package/dist/ssr/layout/index.js.map +1 -1
  91. package/dist/ssr/surfaces/Avatar.js +3 -1
  92. package/dist/ssr/surfaces/Avatar.js.map +1 -1
  93. package/dist/ssr/surfaces/AvatarStack.js +61 -32
  94. package/dist/ssr/surfaces/AvatarStack.js.map +1 -1
  95. package/dist/ssr/surfaces/ChangeList.js +37 -0
  96. package/dist/ssr/surfaces/ChangeList.js.map +1 -0
  97. package/dist/ssr/surfaces/index.js +2 -1
  98. package/dist/ssr/surfaces/index.js.map +1 -1
  99. package/dist/ssr/text-editor/TextEditor.js +19 -8
  100. package/dist/ssr/text-editor/TextEditor.js.map +1 -1
  101. package/dist/styles.css +1 -1
  102. package/dist/types/actions/FilterChip.d.ts +4 -0
  103. package/dist/types/actions/ViewTabs.d.ts +11 -1
  104. package/dist/types/chat/ChatDecision.d.ts +5 -0
  105. package/dist/types/content/FileGrid.d.ts +6 -0
  106. package/dist/types/inputs/Combobox.d.ts +2 -0
  107. package/dist/types/inputs/choice.d.ts +9 -3
  108. package/dist/types/layout/AppWorkspace.d.ts +2 -28
  109. package/dist/types/layout/AppWorkspaceSidebarItemStatus.d.ts +30 -0
  110. package/dist/types/layout/DetailPanel.d.ts +9 -0
  111. package/dist/types/layout/DetailSections.d.ts +186 -0
  112. package/dist/types/layout/DetailsToggle.d.ts +13 -0
  113. package/dist/types/layout/MobileShell.d.ts +12 -0
  114. package/dist/types/layout/PageTitle.d.ts +7 -0
  115. package/dist/types/layout/detail-sections-messages.d.ts +61 -0
  116. package/dist/types/layout/index.d.ts +5 -1
  117. package/dist/types/surfaces/Avatar.d.ts +3 -0
  118. package/dist/types/surfaces/AvatarStack.d.ts +20 -1
  119. package/dist/types/surfaces/ChangeList.d.ts +20 -0
  120. package/dist/types/surfaces/index.d.ts +5 -3
  121. package/package.json +3 -3
@@ -1,5 +1,6 @@
1
1
  import { template as _$template } from "solid-js/web";
2
2
  import { memo as _$memo } from "solid-js/web";
3
+ import { setProperty as _$setProperty } from "solid-js/web";
3
4
  import { setAttribute as _$setAttribute } from "solid-js/web";
4
5
  import { className as _$className } from "solid-js/web";
5
6
  import { effect as _$effect } from "solid-js/web";
@@ -9,23 +10,31 @@ import { insert as _$insert } from "solid-js/web";
9
10
  import { createComponent as _$createComponent } from "solid-js/web";
10
11
  import { use as _$use } from "solid-js/web";
11
12
  var _tmpl$ = /*#__PURE__*/_$template(`<div><!$><!/><!$><!/><main class=k2b-mobile-shell__main></main><!$><!/>`),
12
- _tmpl$2 = /*#__PURE__*/_$template(`<span class=k2b-mobile-shell__title-narrow>`),
13
- _tmpl$3 = /*#__PURE__*/_$template(`<span class=k2b-mobile-shell__title-wide>`),
14
- _tmpl$4 = /*#__PURE__*/_$template(`<div class=k2b-mobile-shell__actions>`),
15
- _tmpl$5 = /*#__PURE__*/_$template(`<header class=k2b-mobile-shell__header><!$><!/><h1 class=k2b-mobile-shell__title></h1><!$><!/>`),
16
- _tmpl$6 = /*#__PURE__*/_$template(`<i class="ti ti-chevron-left"aria-hidden=true>`),
17
- _tmpl$7 = /*#__PURE__*/_$template(`<a><i aria-hidden=true></i><span class=k2b-truncate-fade></span><!$><!/>`),
18
- _tmpl$8 = /*#__PURE__*/_$template(`<span class=k2b-mobile-shell__sidebar-count>`),
19
- _tmpl$9 = /*#__PURE__*/_$template(`<ul>`),
20
- _tmpl$0 = /*#__PURE__*/_$template(`<li>`),
21
- _tmpl$1 = /*#__PURE__*/_$template(`<div class=k2b-mobile-shell__sidebar-foot>`),
22
- _tmpl$10 = /*#__PURE__*/_$template(`<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><!$><!/>`),
23
- _tmpl$11 = /*#__PURE__*/_$template(`<img alt width=32 height=32>`),
24
- _tmpl$12 = /*#__PURE__*/_$template(`<section><h2></h2><!$><!/>`);
13
+ _tmpl$2 = /*#__PURE__*/_$template(`<style>`),
14
+ _tmpl$3 = /*#__PURE__*/_$template(`<span class=k2b-mobile-shell__title-narrow>`),
15
+ _tmpl$4 = /*#__PURE__*/_$template(`<span class=k2b-mobile-shell__title-wide>`),
16
+ _tmpl$5 = /*#__PURE__*/_$template(`<div class=k2b-mobile-shell__actions>`),
17
+ _tmpl$6 = /*#__PURE__*/_$template(`<header class=k2b-mobile-shell__header><!$><!/><h1 class=k2b-mobile-shell__title></h1><!$><!/>`),
18
+ _tmpl$7 = /*#__PURE__*/_$template(`<i class="ti ti-chevron-left"aria-hidden=true>`),
19
+ _tmpl$8 = /*#__PURE__*/_$template(`<a><i aria-hidden=true></i><span class=k2b-truncate-fade></span><!$><!/>`),
20
+ _tmpl$9 = /*#__PURE__*/_$template(`<span class=k2b-mobile-shell__sidebar-count>`),
21
+ _tmpl$0 = /*#__PURE__*/_$template(`<ul>`),
22
+ _tmpl$1 = /*#__PURE__*/_$template(`<li>`),
23
+ _tmpl$10 = /*#__PURE__*/_$template(`<div class=k2b-mobile-shell__sidebar-foot>`),
24
+ _tmpl$11 = /*#__PURE__*/_$template(`<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><!$><!/>`),
25
+ _tmpl$12 = /*#__PURE__*/_$template(`<img alt width=32 height=32>`),
26
+ _tmpl$13 = /*#__PURE__*/_$template(`<section><h2></h2><!$><!/>`);
25
27
  import { For, onCleanup, onMount, Show } from "solid-js";
26
28
  import { IconButton, IconButtonLink } from "../actions/Button";
27
29
  import ScrollArea from "./ScrollArea";
28
30
  import { installScrollFades } from "./scroll-fade";
31
+ /**
32
+ * On the document element while a shell is the page: `"sidebar"` when it shows its sidebar, otherwise empty. A host
33
+ * that renders the shell on the server writes it into its `<html>` too, so the first paint already has it.
34
+ */
35
+ const SHELL_ATTRIBUTE = "data-k2b-mobile-shell";
36
+ /** On the document element while a shell is mounted and a modal dialog is open. */
37
+ const MODAL_ATTRIBUTE = "data-k2b-mobile-shell-modal";
29
38
  /** Read by the toast rail, which is portalled outside the shell, so it lives on the body. */
30
39
  const FOOTER_HEIGHT = "--k2b-mobile-shell-footer-height";
31
40
  /** How much of the scroll area a persistent toast covers; local to the shell. */
@@ -45,6 +54,19 @@ const persistentToastInset = body => {
45
54
  return Math.max(0, Math.min(area.height, area.bottom - top));
46
55
  };
47
56
 
57
+ /** On the root while its content is a screen of its own; the footer steps aside. */
58
+ const SCREEN = "data-k2b-screen";
59
+
60
+ /**
61
+ * Marks the shell around `from` as showing a screen of its own, or not, after the first paint; the server renders
62
+ * the first state with the `screen` prop. Does nothing outside a shell.
63
+ */
64
+ const setMobileShellScreen = (from, screen) => {
65
+ const root = from.closest(".k2b-mobile-shell");
66
+ if (!root) return;
67
+ if (screen) root.setAttribute(SCREEN, "");else root.removeAttribute(SCREEN);
68
+ };
69
+
48
70
  /** Marks the link whose page is loading; the tab bar shows it as selected. */
49
71
  const PENDING = "data-k2b-pending";
50
72
  /** 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. */
@@ -165,6 +187,11 @@ export function observeMobileShell(root) {
165
187
  const body = main?.querySelector(":scope > .k2b-mobile-shell__body");
166
188
  if (!main) return () => {};
167
189
  const stopLinkTaps = observeLinkTaps(root);
190
+ // The document says that a shell is the page, for the controls and toasts portalled outside it. An attribute, not
191
+ // `html:has(.k2b-mobile-shell)`: a `:has()` on the document restyles the whole page on every change inside it.
192
+ const documentRoot = root.ownerDocument.documentElement;
193
+ const previousShell = documentRoot.getAttribute(SHELL_ATTRIBUTE);
194
+ documentRoot.setAttribute(SHELL_ATTRIBUTE, root.querySelector(":scope > .k2b-mobile-shell__sidebar") ? "sidebar" : "");
168
195
  // Content with its own scroll areas keeps its own fades; the room a toast takes applies to the whole main area.
169
196
  const stopScrollFades = body ? installScrollFades(body) : () => {};
170
197
  let frame = 0;
@@ -186,6 +213,8 @@ export function observeMobileShell(root) {
186
213
  const footer = Math.max(0, rootBottom - main.getBoundingClientRect().bottom, owned);
187
214
  document.body.style.setProperty(FOOTER_HEIGHT, `${footer}px`);
188
215
  root.style.setProperty(TOAST_INSET, `${persistentToastInset(body ?? main)}px`);
216
+ // A modal dialog takes the bottom edge back for its footer; the toast rules read this, not `html:has(dialog:modal)`.
217
+ documentRoot.toggleAttribute(MODAL_ATTRIBUTE, document.querySelector("dialog:modal") !== null);
189
218
  };
190
219
  const schedule = () => {
191
220
  if (!frame) frame = requestAnimationFrame(measure);
@@ -211,6 +240,8 @@ export function observeMobileShell(root) {
211
240
  mutations.disconnect();
212
241
  document.removeEventListener("transitionend", schedule, true);
213
242
  document.body.style.removeProperty(FOOTER_HEIGHT);
243
+ documentRoot.removeAttribute(MODAL_ATTRIBUTE);
244
+ if (previousShell === null) documentRoot.removeAttribute(SHELL_ATTRIBUTE);else documentRoot.setAttribute(SHELL_ATTRIBUTE, previousShell);
214
245
  stopLinkTaps();
215
246
  stopScrollFades();
216
247
  };
@@ -218,7 +249,7 @@ export function observeMobileShell(root) {
218
249
  function MobileShellRoot(props) {
219
250
  let root;
220
251
  onMount(() => onCleanup(observeMobileShell(root)));
221
- return (() => {
252
+ return [(() => {
222
253
  var _el$ = _$getNextElement(_tmpl$),
223
254
  _el$3 = _el$.firstChild,
224
255
  [_el$4, _co$] = _$getNextMarker(_el$3.nextSibling),
@@ -250,26 +281,33 @@ function MobileShellRoot(props) {
250
281
  _$insert(_el$, () => props.footer, _el$8, _co$3);
251
282
  _$effect(_p$ => {
252
283
  var _v$ = props.class ? `k2b-mobile-shell ${props.class}` : "k2b-mobile-shell",
253
- _v$2 = props.scroll === false ? "" : undefined;
284
+ _v$2 = props.scroll === false ? "" : undefined,
285
+ _v$3 = props.screen ? "" : undefined;
254
286
  _v$ !== _p$.e && _$className(_el$, _p$.e = _v$);
255
287
  _v$2 !== _p$.t && _$setAttribute(_el$, "data-k2b-content-heads", _p$.t = _v$2);
288
+ _v$3 !== _p$.a && _$setAttribute(_el$, "data-k2b-screen", _p$.a = _v$3);
256
289
  return _p$;
257
290
  }, {
258
291
  e: undefined,
259
- t: undefined
292
+ t: undefined,
293
+ a: undefined
260
294
  });
261
295
  return _el$;
262
- })();
296
+ })(), (() => {
297
+ var _el$9 = _$getNextElement(_tmpl$2);
298
+ _$setProperty(_el$9, "innerHTML", ":where(html *) { touch-action: pan-x pan-y; }");
299
+ return _el$9;
300
+ })()];
263
301
  }
264
302
  function MobileShellHeader(props) {
265
303
  return (() => {
266
- var _el$9 = _$getNextElement(_tmpl$5),
267
- _el$12 = _el$9.firstChild,
268
- [_el$13, _co$4] = _$getNextMarker(_el$12.nextSibling),
269
- _el$0 = _el$13.nextSibling,
270
- _el$14 = _el$0.nextSibling,
271
- [_el$15, _co$5] = _$getNextMarker(_el$14.nextSibling);
272
- _$insert(_el$9, _$createComponent(Show, {
304
+ var _el$0 = _$getNextElement(_tmpl$6),
305
+ _el$13 = _el$0.firstChild,
306
+ [_el$14, _co$4] = _$getNextMarker(_el$13.nextSibling),
307
+ _el$1 = _el$14.nextSibling,
308
+ _el$15 = _el$1.nextSibling,
309
+ [_el$16, _co$5] = _$getNextMarker(_el$15.nextSibling);
310
+ _$insert(_el$0, _$createComponent(Show, {
273
311
  get when() {
274
312
  return props.back;
275
313
  },
@@ -289,7 +327,7 @@ function MobileShellHeader(props) {
289
327
  tooltip: false,
290
328
  onClick: () => back().onClick?.(),
291
329
  get children() {
292
- return _$getNextElement(_tmpl$6);
330
+ return _$getNextElement(_tmpl$7);
293
331
  }
294
332
  });
295
333
  },
@@ -307,12 +345,12 @@ function MobileShellHeader(props) {
307
345
  tooltip: false,
308
346
  onClick: () => back().onClick?.(),
309
347
  get children() {
310
- return _$getNextElement(_tmpl$6);
348
+ return _$getNextElement(_tmpl$7);
311
349
  }
312
350
  })
313
351
  })
314
- }), _el$13, _co$4);
315
- _$insert(_el$0, _$createComponent(Show, {
352
+ }), _el$14, _co$4);
353
+ _$insert(_el$1, _$createComponent(Show, {
316
354
  get when() {
317
355
  return _$memo(() => props.sidebarTitle !== undefined)() && props.sidebarTitle !== props.title;
318
356
  },
@@ -321,58 +359,58 @@ function MobileShellHeader(props) {
321
359
  },
322
360
  get children() {
323
361
  return [(() => {
324
- var _el$1 = _$getNextElement(_tmpl$2);
325
- _$insert(_el$1, () => props.title);
326
- return _el$1;
327
- })(), (() => {
328
362
  var _el$10 = _$getNextElement(_tmpl$3);
329
- _$insert(_el$10, () => props.sidebarTitle);
363
+ _$insert(_el$10, () => props.title);
330
364
  return _el$10;
365
+ })(), (() => {
366
+ var _el$11 = _$getNextElement(_tmpl$4);
367
+ _$insert(_el$11, () => props.sidebarTitle);
368
+ return _el$11;
331
369
  })()];
332
370
  }
333
371
  }));
334
- _$insert(_el$9, _$createComponent(Show, {
372
+ _$insert(_el$0, _$createComponent(Show, {
335
373
  get when() {
336
374
  return props.actions;
337
375
  },
338
376
  get children() {
339
- var _el$11 = _$getNextElement(_tmpl$4);
340
- _$insert(_el$11, () => props.actions);
341
- return _el$11;
377
+ var _el$12 = _$getNextElement(_tmpl$5);
378
+ _$insert(_el$12, () => props.actions);
379
+ return _el$12;
342
380
  }
343
- }), _el$15, _co$5);
344
- return _el$9;
381
+ }), _el$16, _co$5);
382
+ return _el$0;
345
383
  })();
346
384
  }
347
385
  function SidebarLink(props) {
348
386
  return (() => {
349
- var _el$18 = _$getNextElement(_tmpl$7),
350
- _el$19 = _el$18.firstChild,
351
- _el$20 = _el$19.nextSibling,
387
+ var _el$19 = _$getNextElement(_tmpl$8),
388
+ _el$20 = _el$19.firstChild,
352
389
  _el$21 = _el$20.nextSibling,
353
- [_el$22, _co$6] = _$getNextMarker(_el$21.nextSibling);
354
- _$insert(_el$20, () => props.item.label);
355
- _$insert(_el$18, _$createComponent(Show, {
390
+ _el$22 = _el$21.nextSibling,
391
+ [_el$23, _co$6] = _$getNextMarker(_el$22.nextSibling);
392
+ _$insert(_el$21, () => props.item.label);
393
+ _$insert(_el$19, _$createComponent(Show, {
356
394
  get when() {
357
395
  return props.item.badge ?? props.item.count;
358
396
  },
359
397
  children: mark => (() => {
360
- var _el$23 = _$getNextElement(_tmpl$8);
361
- _$insert(_el$23, mark);
362
- return _el$23;
398
+ var _el$24 = _$getNextElement(_tmpl$9);
399
+ _$insert(_el$24, mark);
400
+ return _el$24;
363
401
  })()
364
- }), _el$22, _co$6);
402
+ }), _el$23, _co$6);
365
403
  _$effect(_p$ => {
366
- var _v$3 = props.item.href,
367
- _v$4 = props.item.current ? "page" : undefined,
368
- _v$5 = props.item.id,
369
- _v$6 = props.item.title ?? props.item.label,
370
- _v$7 = props.item.icon;
371
- _v$3 !== _p$.e && _$setAttribute(_el$18, "href", _p$.e = _v$3);
372
- _v$4 !== _p$.t && _$setAttribute(_el$18, "aria-current", _p$.t = _v$4);
373
- _v$5 !== _p$.a && _$setAttribute(_el$18, "data-tab", _p$.a = _v$5);
374
- _v$6 !== _p$.o && _$setAttribute(_el$18, "data-k2b-title", _p$.o = _v$6);
375
- _v$7 !== _p$.i && _$className(_el$19, _p$.i = _v$7);
404
+ var _v$4 = props.item.href,
405
+ _v$5 = props.item.current ? "page" : undefined,
406
+ _v$6 = props.item.id,
407
+ _v$7 = props.item.title ?? props.item.label,
408
+ _v$8 = props.item.icon;
409
+ _v$4 !== _p$.e && _$setAttribute(_el$19, "href", _p$.e = _v$4);
410
+ _v$5 !== _p$.t && _$setAttribute(_el$19, "aria-current", _p$.t = _v$5);
411
+ _v$6 !== _p$.a && _$setAttribute(_el$19, "data-tab", _p$.a = _v$6);
412
+ _v$7 !== _p$.o && _$setAttribute(_el$19, "data-k2b-title", _p$.o = _v$7);
413
+ _v$8 !== _p$.i && _$className(_el$20, _p$.i = _v$8);
376
414
  return _p$;
377
415
  }, {
378
416
  e: undefined,
@@ -381,7 +419,7 @@ function SidebarLink(props) {
381
419
  o: undefined,
382
420
  i: undefined
383
421
  });
384
- return _el$18;
422
+ return _el$19;
385
423
  })();
386
424
  }
387
425
 
@@ -392,70 +430,70 @@ function SidebarLink(props) {
392
430
  */
393
431
  function MobileShellSidebar(props) {
394
432
  const list = items => (() => {
395
- var _el$24 = _$getNextElement(_tmpl$9);
396
- _$insert(_el$24, _$createComponent(For, {
433
+ var _el$25 = _$getNextElement(_tmpl$0);
434
+ _$insert(_el$25, _$createComponent(For, {
397
435
  each: items,
398
436
  children: item => (() => {
399
- var _el$25 = _$getNextElement(_tmpl$0);
400
- _$insert(_el$25, _$createComponent(SidebarLink, {
437
+ var _el$26 = _$getNextElement(_tmpl$1);
438
+ _$insert(_el$26, _$createComponent(SidebarLink, {
401
439
  item: item
402
440
  }));
403
- return _el$25;
441
+ return _el$26;
404
442
  })()
405
443
  }));
406
- return _el$24;
444
+ return _el$25;
407
445
  })();
408
446
  return (() => {
409
- var _el$26 = _$getNextElement(_tmpl$10),
410
- _el$27 = _el$26.firstChild,
411
- _el$29 = _el$27.firstChild,
412
- [_el$30, _co$7] = _$getNextMarker(_el$29.nextSibling),
413
- _el$28 = _el$30.nextSibling,
414
- _el$31 = _el$27.nextSibling,
415
- _el$32 = _el$31.firstChild,
416
- [_el$33, _co$8] = _$getNextMarker(_el$32.nextSibling),
417
- _el$34 = _el$33.nextSibling,
418
- [_el$35, _co$9] = _$getNextMarker(_el$34.nextSibling),
419
- _el$37 = _el$31.nextSibling,
420
- [_el$38, _co$0] = _$getNextMarker(_el$37.nextSibling);
421
- _$insert(_el$27, _$createComponent(Show, {
447
+ var _el$27 = _$getNextElement(_tmpl$11),
448
+ _el$28 = _el$27.firstChild,
449
+ _el$30 = _el$28.firstChild,
450
+ [_el$31, _co$7] = _$getNextMarker(_el$30.nextSibling),
451
+ _el$29 = _el$31.nextSibling,
452
+ _el$32 = _el$28.nextSibling,
453
+ _el$33 = _el$32.firstChild,
454
+ [_el$34, _co$8] = _$getNextMarker(_el$33.nextSibling),
455
+ _el$35 = _el$34.nextSibling,
456
+ [_el$36, _co$9] = _$getNextMarker(_el$35.nextSibling),
457
+ _el$38 = _el$32.nextSibling,
458
+ [_el$39, _co$0] = _$getNextMarker(_el$38.nextSibling);
459
+ _$insert(_el$28, _$createComponent(Show, {
422
460
  get when() {
423
461
  return props.logo;
424
462
  },
425
463
  children: logo => (() => {
426
- var _el$39 = _$getNextElement(_tmpl$11);
427
- _$effect(() => _$setAttribute(_el$39, "src", logo()));
428
- return _el$39;
464
+ var _el$40 = _$getNextElement(_tmpl$12);
465
+ _$effect(() => _$setAttribute(_el$40, "src", logo()));
466
+ return _el$40;
429
467
  })()
430
- }), _el$30, _co$7);
431
- _$insert(_el$28, () => props.title);
432
- _$insert(_el$31, () => list(props.items), _el$33, _co$8);
433
- _$insert(_el$31, _$createComponent(For, {
468
+ }), _el$31, _co$7);
469
+ _$insert(_el$29, () => props.title);
470
+ _$insert(_el$32, () => list(props.items), _el$34, _co$8);
471
+ _$insert(_el$32, _$createComponent(For, {
434
472
  get each() {
435
473
  return (props.sections ?? []).filter(section => section.items.length > 0);
436
474
  },
437
475
  children: section => (() => {
438
- var _el$40 = _$getNextElement(_tmpl$12),
439
- _el$41 = _el$40.firstChild,
440
- _el$42 = _el$41.nextSibling,
441
- [_el$43, _co$1] = _$getNextMarker(_el$42.nextSibling);
442
- _$insert(_el$41, () => section.title);
443
- _$insert(_el$40, () => list(section.items), _el$43, _co$1);
444
- return _el$40;
476
+ var _el$41 = _$getNextElement(_tmpl$13),
477
+ _el$42 = _el$41.firstChild,
478
+ _el$43 = _el$42.nextSibling,
479
+ [_el$44, _co$1] = _$getNextMarker(_el$43.nextSibling);
480
+ _$insert(_el$42, () => section.title);
481
+ _$insert(_el$41, () => list(section.items), _el$44, _co$1);
482
+ return _el$41;
445
483
  })()
446
- }), _el$35, _co$9);
447
- _$insert(_el$26, _$createComponent(Show, {
484
+ }), _el$36, _co$9);
485
+ _$insert(_el$27, _$createComponent(Show, {
448
486
  get when() {
449
487
  return props.footer?.length;
450
488
  },
451
489
  get children() {
452
- var _el$36 = _$getNextElement(_tmpl$1);
453
- _$insert(_el$36, () => list(props.footer ?? []));
454
- return _el$36;
490
+ var _el$37 = _$getNextElement(_tmpl$10);
491
+ _$insert(_el$37, () => list(props.footer ?? []));
492
+ return _el$37;
455
493
  }
456
- }), _el$38, _co$0);
457
- _$effect(() => _$setAttribute(_el$26, "aria-label", props.label));
458
- return _el$26;
494
+ }), _el$39, _co$0);
495
+ _$effect(() => _$setAttribute(_el$27, "aria-label", props.label));
496
+ return _el$27;
459
497
  })();
460
498
  }
461
499
 
@@ -467,7 +505,8 @@ function MobileShellSidebar(props) {
467
505
  */
468
506
  export const MobileShell = Object.assign(MobileShellRoot, {
469
507
  Header: MobileShellHeader,
470
- Sidebar: MobileShellSidebar
508
+ Sidebar: MobileShellSidebar,
509
+ setScreen: setMobileShellScreen
471
510
  });
472
511
  export default MobileShell;
473
512
  //# sourceMappingURL=MobileShell.js.map
@@ -1 +1 @@
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":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,GAAG,EAAY,SAAS,EAAE,OAAO,EAAE,IAAI,QAAQ,UAAU;AAClE,SAAS,UAAU,EAAE,cAAc,QAAQ,mBAAmB;AAC9D,OAAO,UAAU,MAAM,cAAc;AACrC,SAAS,kBAAkB,QAAQ,eAAe;AAqClD;AACA,MAAM,aAAa,GAAG,kCAAkC;AACxD;AACA,MAAM,WAAW,GAAG,gCAAgC;;AAEpD;AACA;AACA;AACA;AACA;AACA,MAAM,oBAAoB,GAAI,IAAiB,IAAa;EAC1D,IAAI,QAAQ,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;EACpD,MAAM,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAc,iEAAiE,CAAC,CAAC;EAC5H,IAAI,CAAC,KAAK,CAAC,IAAI,CAAE,IAAI,IAAK,IAAI,CAAC,OAAO,CAAC,UAAU,KAAK,MAAM,CAAC,EAAE,OAAO,CAAC;EACvE,MAAM,IAAI,GAAG,IAAI,CAAC,qBAAqB,CAAC,CAAC;EACzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAE,IAAI,IAAK,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;EAC9E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;AAC9D,CAAC;;AAED;AACA,MAAM,OAAO,GAAG,kBAAkB;AAClC;AACA,MAAM,UAAU,GAAG,gBAAgB;AACnC;AACA,MAAM,SAAS,GAAG,oBAAoB;AACtC;AACA,MAAM,UAAU,GAAG,qBAAqB;;AAExC;AACA,MAAM,QAAQ,GAAG,CAAC,IAAiB,EAAE,KAAiB,KAA+B;EACnF,IAAI,KAAK,CAAC,gBAAgB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,EAAE,OAAO,IAAI;EACjI,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,YAAY,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAoB,SAAS,CAAC,GAAG,IAAI;EACxG,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,IAAK,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,OAAQ,EAAE,OAAO,IAAI;EAC3H,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;EAC9B,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,OAAO,IAAI;EAC/C;EACA,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,OAAO,IAAI;EACjG,OAAO,IAAI;AACb,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,eAAe,GAAI,IAAiB,IAAmB;EAC3D;EACA,IAAI,OAA2B;EAC/B;EACA,IAAI,OAAO,GAAG,KAAK;EACnB,MAAM,KAAK,GAAG,MAAM;IAClB,OAAO,GAAG,SAAS;IACnB,OAAO,GAAG,KAAK;IACf,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;IACvF,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC;IAC/B,IAAI,CAAC,aAAa,CAAC,0BAA0B,CAAC,EAAE,eAAe,CAAC,UAAU,CAAC;EAC7E,CAAC;EACD,MAAM,IAAI,GAAI,IAAuB,IAAK;IACxC,KAAK,CAAC,CAAC;IACP,OAAO,GAAG,IAAI,CAAC,IAAI;IACnB,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,0BAA0B,CAAC;IAC9D,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE;IAChC,OAAO,CAAC,YAAY,CAAC,UAAU,EAAE,KAAK,CAAC;IACvC,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,CAAC;EAClC,CAAC;EACD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC;EACtB;EACA;EACA;EACA,MAAM,KAAK,GAAI,KAAmB,IAAK;IACrC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE;IACtB,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAClC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,UAAU,CAAC,IAAK,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,OAAQ,EAAE;IAC3E,IAAI,CAAC,IAAI,CAAC;EACZ,CAAC;EACD;EACA;EACA,MAAM,MAAM,GAAI,KAAmB,IAAK;IACtC,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;EAC1C,CAAC;EACD;EACA,MAAM,KAAK,GAAI,KAAiB,IAAK;IACnC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAClC,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,EAAE;MAC5C,KAAK,CAAC,cAAc,CAAC,CAAC;MACtB;IACF;IACA;IACA,KAAK,CAAC,CAAC;IACP,IAAI,CAAC,IAAI,EAAE;IACX,IAAI,CAAC,IAAI,CAAC;IACV,OAAO,GAAG,IAAI;EAChB,CAAC;EACD;EACA;EACA,MAAM,OAAO,GAAI,KAAmB,IAAK;IACvC,IAAI,OAAO,KAAK,SAAS,EAAE;IAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,YAAY,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAoB,SAAS,CAAC,GAAG,IAAI;IACxG,IAAI,IAAI,EAAE,IAAI,KAAK,OAAO,EAAE,KAAK,CAAC,CAAC;EACrC,CAAC;EACD;EACA,MAAM,IAAI,GAAI,KAA0B,IAAK;IAC3C,IAAI,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;EAC9B,CAAC;EACD,QAAQ,CAAC,gBAAgB,CAAC,YAAY,EAAE,KAAK,EAAE;IAAE,OAAO,EAAE;EAAK,CAAC,CAAC;EACjE,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,KAAK,CAAC;EAC7C,MAAM,CAAC,gBAAgB,CAAC,eAAe,EAAE,MAAM,CAAC;EAChD,MAAM,CAAC,gBAAgB,CAAC,WAAW,EAAE,OAAO,CAAC;EAC7C,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC;EACvC,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC;EACzC,OAAO,MAAM;IACX,QAAQ,CAAC,mBAAmB,CAAC,YAAY,EAAE,KAAK,CAAC;IACjD,MAAM,CAAC,mBAAmB,CAAC,aAAa,EAAE,KAAK,CAAC;IAChD,MAAM,CAAC,mBAAmB,CAAC,eAAe,EAAE,MAAM,CAAC;IACnD,MAAM,CAAC,mBAAmB,CAAC,WAAW,EAAE,OAAO,CAAC;IAChD,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC;IAC1C,MAAM,CAAC,mBAAmB,CAAC,UAAU,EAAE,IAAI,CAAC;IAC5C,KAAK,CAAC,CAAC;EACT,CAAC;AACH,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAAS,kBAAkB,CAAC,IAAiB,EAAc;EAChE,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAc,kCAAkC,CAAC;EAChF,MAAM,IAAI,GAAG,IAAI,EAAE,aAAa,CAAc,kCAAkC,CAAC;EACjF,IAAI,CAAC,IAAI,EAAE,OAAO,MAAM,CAAC,CAAC;EAC1B,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,CAAC;EAC1C;EACA,MAAM,eAAe,GAAG,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;EAClE,IAAI,KAAK,GAAG,CAAC;EACb,IAAI,KAAyB,GAAG,IAAI;EACpC,IAAI,MAAkC;EACtC,MAAM,UAAU,GAAG,MAAM;IACvB,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAc,yBAAyB,CAAC;IACvE,IAAI,IAAI,KAAK,KAAK,EAAE;IACpB,IAAI,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC;IACnC,KAAK,GAAG,IAAI;IACZ,IAAI,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC;EACnC,CAAC;EACD,MAAM,OAAO,GAAG,MAAM;IACpB,KAAK,GAAG,CAAC;IACT,UAAU,CAAC,CAAC;IACZ,MAAM,UAAU,GAAG,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,MAAM;IACtD;IACA,MAAM,KAAK,GAAG,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,MAAM,GAAG,UAAU,GAAG,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;IACjG,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC;IACnF,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,aAAa,EAAE,GAAG,MAAM,IAAI,CAAC;IAC7D,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,GAAG,oBAAoB,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;EAChF,CAAC;EACD,MAAM,QAAQ,GAAG,MAAM;IACrB,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC;EACpD,CAAC;EACD;EACA;EACA,MAAM,GAAG,IAAI,cAAc,CAAC,QAAQ,CAAC;EACrC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;EACpB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;EACpB,OAAO,CAAC,CAAC;EACT;EACA,MAAM,SAAS,GAAG,IAAI,gBAAgB,CAAC,QAAQ,CAAC;EAChD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE;IAC/B,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,IAAI;IAChB,eAAe,EAAE,CAAC,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM;EAC3E,CAAC,CAAC;EACF,QAAQ,CAAC,gBAAgB,CAAC,eAAe,EAAE,QAAQ,EAAE,IAAI,CAAC;EAC1D,OAAO,MAAM;IACX,IAAI,KAAK,EAAE,oBAAoB,CAAC,KAAK,CAAC;IACtC,MAAM,CAAC,UAAU,CAAC,CAAC;IACnB,SAAS,CAAC,UAAU,CAAC,CAAC;IACtB,QAAQ,CAAC,mBAAmB,CAAC,eAAe,EAAE,QAAQ,EAAE,IAAI,CAAC;IAC7D,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC;IACjD,YAAY,CAAC,CAAC;IACd,eAAe,CAAC,CAAC;EACnB,CAAC;AACH;AAEA,SAAS,eAAe,CAAC,KAAuB,EAAe;EAC7D,IAAI,IAAqB;EAEzB,OAAO,CAAC,MAAM,SAAS,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;EAElD;IAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;IAAA,YAES,IAAI;IAAA,mDAAJ,IAAI;IAAA,qBAIR,KAAK,CAAC,OAAO;IAAA,qBACb,KAAK,CAAC,MAAM;IAAA,kCAEV,IAAI;MAAA,IAAC,IAAI;QAAA,OAAE,KAAK,CAAC,MAAM,KAAK,KAAK;MAAA;MAAA,IAAE,QAAQ;QAAA,OAAE,KAAK,CAAC,QAAQ;MAAA;MAAA;QAAA,yBACzD,UAAU;UAAA;UAAA;YAAA,OAAiC,KAAK,CAAC,QAAQ;UAAA;QAAA;MAAA;IAAA;IAAA,qBAG7D,KAAK,CAAC,MAAM;IAAA;MAAA,UAVN,KAAK,CAAC,KAAK,GAAG,oBAAoB,KAAK,CAAC,KAAK,EAAE,GAAG,kBAAkB;QAAA,OACnD,KAAK,CAAC,MAAM,KAAK,KAAK,GAAG,EAAE,GAAG,SAAS;MAAA;MAAA;MAAA;IAAA;MAAA;MAAA;IAAA;IAAA;EAAA;AAYrE;AAEA,SAAS,iBAAiB,CAAC,KAA6B,EAAe;EACrE;IAAA;MAAA;MAAA;MAAA;MAAA;MAAA;IAAA,kCAEK,IAAI;MAAA,IAAC,IAAI;QAAA,OAAE,KAAK,CAAC,IAAI;MAAA;MAAA,UAClB,IAAI,sBACH,IAAI;QAAA,IACH,IAAI;UAAA,OAAE,IAAI,CAAC,CAAC,CAAC,IAAI;QAAA;QAAA,IACjB,QAAQ;UAAA,yBACL,UAAU;YAAA;YAAA;cAAA,OAEU,IAAI,CAAC,CAAC,CAAC,WAAW,KAAK,KAAK,GAAG,OAAO,GAAG,SAAS;YAAA;YAAA,IACrE,KAAK;cAAA,OAAE,IAAI,CAAC,CAAC,CAAC,KAAK;YAAA;YACnB,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC;YAAA;cAAA;YAAA;UAAA;QAAA;QAAA,UAMnC,IAAI,sBACH,cAAc;UAAA;UAAA;YAAA,OAEM,IAAI,CAAC,CAAC,CAAC,WAAW,KAAK,KAAK,GAAG,OAAO,GAAG,SAAS;UAAA;UAAA,IACrE,IAAI;YAAA,OAAE,IAAI,CAAC,CAAC;UAAA;UAAA,IACZ,KAAK;YAAA,OAAE,IAAI,CAAC,CAAC,CAAC,KAAK;UAAA;UACnB,OAAO,EAAE,KAAK;UACd,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC;UAAA;YAAA;UAAA;QAAA;MAIpC;IAEJ;IAAA,kCAGA,IAAI;MAAA,IAAC,IAAI;QAAA,OAAE,kBAAK,CAAC,YAAY,KAAK,SAAS,OAAI,KAAK,CAAC,YAAY,KAAK,KAAK,CAAC,KAAK;MAAA;MAAA,IAAE,QAAQ;QAAA,OAAE,KAAK,CAAC,KAAK;MAAA;MAAA;QAAA;UAAA;UAAA,sBACzD,KAAK,CAAC,KAAK;UAAA;QAAA;UAAA;UAAA,uBACb,KAAK,CAAC,YAAY;UAAA;QAAA;MAAA;IAAA;IAAA,kCAGjE,IAAI;MAAA,IAAC,IAAI;QAAA,OAAE,KAAK,CAAC,OAAO;MAAA;MAAA;QAAA;QAAA,uBACiB,KAAK,CAAC,OAAO;QAAA;MAAA;IAAA;IAAA;EAAA;AAI7D;AAyBA,SAAS,WAAW,CAAC,KAAuC,EAAe;EACzE;IAAA;MAAA;MAAA;MAAA;MAAA;IAAA,uBAQqC,KAAK,CAAC,IAAI,CAAC,KAAK;IAAA,mCAChD,IAAI;MAAA,IAAC,IAAI;QAAA,OAAE,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK;MAAA;MAAA,UAAI,IAAI;QAAA;QAAA,iBAAoD,IAAI;QAAA;MAAA;IAAU;IAAA;MAAA,WAPpH,KAAK,CAAC,IAAI,CAAC,IAAI;QAAA,OACP,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,MAAM,GAAG,SAAS;QAAA,OAC3C,KAAK,CAAC,IAAI,CAAC,EAAE;QAAA,OACP,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK;QAAA,OAE1C,KAAK,CAAC,IAAI,CAAC,IAAI;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;IAAA;MAAA;MAAA;MAAA;MAAA;MAAA;IAAA;IAAA;EAAA;AAK/B;;AAEA;AACA;AACA;AACA;AACA;AACA,SAAS,kBAAkB,CAAC,KAA8B,EAAe;EACvE,MAAM,IAAI,GAAI,KAAwC;IAAA;IAAA,mCAEjD,GAAG;MAAC,IAAI,EAAE,KAAK;MAAA,UACZ,IAAI;QAAA;QAAA,mCAED,WAAW;UAAC,IAAI,EAAE;QAAI;QAAA;MAAA;IAE1B;IAAA;EAAA,IAGN;EACD;IAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;IAAA,mCAGO,IAAI;MAAA,IAAC,IAAI;QAAA,OAAE,KAAK,CAAC,IAAI;MAAA;MAAA,UAAI,IAAI;QAAA;QAAA,6CAAe,IAAI,CAAC,CAAC;QAAA;MAAA;IAAkC;IAAA,uBACpD,KAAK,CAAC,KAAK;IAAA,uBAG3C,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;IAAA,mCACjB,GAAG;MAAA,IAAC,IAAI;QAAA,OAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,EAAE,MAAM,CAAE,OAAO,IAAK,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;MAAA;MAAA,UAC3E,OAAO;QAAA;UAAA;UAAA;UAAA;QAAA,uBAEA,OAAO,CAAC,KAAK;QAAA,uBACjB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAAA;MAAA;IAEvB;IAAA,mCAGJ,IAAI;MAAA,IAAC,IAAI;QAAA,OAAE,KAAK,CAAC,MAAM,EAAE,MAAM;MAAA;MAAA;QAAA;QAAA,uBACe,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;QAAA;MAAA;IAAA;IAAA,oDAjBtB,KAAK,CAAC,KAAK;IAAA;EAAA;AAqBlE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE;EAAE,MAAM,EAAE,iBAAiB;EAAE,OAAO,EAAE;AAAmB,CAAC,CAAC;AAErH,eAAe,WAAW","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 /**\n * The content is a screen of its own, such as an open chat on a phone: the footer steps aside, so the content\n * reaches the bottom edge and pads its safe area itself. Content that opens or leaves such a screen in place calls\n * `MobileShell.setScreen`.\n */\n screen?: 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/**\n * On the document element while a shell is the page: `\"sidebar\"` when it shows its sidebar, otherwise empty. A host\n * that renders the shell on the server writes it into its `<html>` too, so the first paint already has it.\n */\nconst SHELL_ATTRIBUTE = \"data-k2b-mobile-shell\";\n/** On the document element while a shell is mounted and a modal dialog is open. */\nconst MODAL_ATTRIBUTE = \"data-k2b-mobile-shell-modal\";\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/** On the root while its content is a screen of its own; the footer steps aside. */\nconst SCREEN = \"data-k2b-screen\";\n\n/**\n * Marks the shell around `from` as showing a screen of its own, or not, after the first paint; the server renders\n * the first state with the `screen` prop. Does nothing outside a shell.\n */\nconst setMobileShellScreen = (from: Element, screen: boolean): void => {\n const root = from.closest(\".k2b-mobile-shell\");\n if (!root) return;\n if (screen) root.setAttribute(SCREEN, \"\");\n else root.removeAttribute(SCREEN);\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 // The document says that a shell is the page, for the controls and toasts portalled outside it. An attribute, not\n // `html:has(.k2b-mobile-shell)`: a `:has()` on the document restyles the whole page on every change inside it.\n const documentRoot = root.ownerDocument.documentElement;\n const previousShell = documentRoot.getAttribute(SHELL_ATTRIBUTE);\n documentRoot.setAttribute(SHELL_ATTRIBUTE, root.querySelector(\":scope > .k2b-mobile-shell__sidebar\") ? \"sidebar\" : \"\");\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 // A modal dialog takes the bottom edge back for its footer; the toast rules read this, not `html:has(dialog:modal)`.\n documentRoot.toggleAttribute(MODAL_ATTRIBUTE, document.querySelector(\"dialog:modal\") !== null);\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 documentRoot.removeAttribute(MODAL_ATTRIBUTE);\n if (previousShell === null) documentRoot.removeAttribute(SHELL_ATTRIBUTE);\n else documentRoot.setAttribute(SHELL_ATTRIBUTE, previousShell);\n stopLinkTaps();\n stopScrollFades();\n };\n}\n\nfunction MobileShellRoot(props: MobileShellProps): JSX.Element {\n let root!: HTMLDivElement;\n\n onMount(() => onCleanup(observeMobileShell(root)));\n return (\n <>\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 data-k2b-screen={props.screen ? \"\" : 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 {/* No pinch zoom on the whole page, portalled dialogs and menus included, also before hydration. iOS ignores\n `user-scalable=no`; it honours touch-action. It lives here, not behind `html:has(.k2b-mobile-shell) *`, which\n would restyle the document on every `:has()` change. Zero specificity, so a component's own touch-action,\n such as a drag handle's `none`, still wins. */}\n <style innerHTML=\":where(html *) { touch-action: pan-x pan-y; }\" />\n </>\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, {\n Header: MobileShellHeader,\n Sidebar: MobileShellSidebar,\n setScreen: setMobileShellScreen,\n});\n\nexport default MobileShell;\n"],"mappings":"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","ignoreList":[]}
@@ -73,27 +73,42 @@ export const setPageTrail = trail => {
73
73
  window.dispatchEvent(new Event(PAGE_TRAIL_EVENT));
74
74
  };
75
75
  const PageTrailContext = createContext();
76
+ const OwnTitleContext = createContext();
77
+ /** The element around a page whose `data-k2b-own-title` says that the page shows its own `PageTitle`. */
78
+ const OWN_TITLE_HOST = "data-k2b-title-host";
79
+ const syncOwnTitle = host => host.toggleAttribute("data-k2b-own-title", host.querySelector(".k2b-page-title:not([data-fallback])") !== null);
76
80
 
77
81
  /**
78
82
  * Gives a host's page trail to everything below it. During server rendering
79
83
  * the context carries it; the provider also publishes it into the document,
80
84
  * where browser-rendered parts such as islands read it. Below a provider that
81
85
  * renders in the browser, its `trail` prop is the source: update the prop.
86
+ *
87
+ * `onOwnTitle` runs on the server for each `PageTitle` below that is not a
88
+ * fallback. The host then sets `data-k2b-own-title` on an element around the
89
+ * page, inside the `k2b-ui` root, so the chrome's fallback titles step back.
90
+ * With `data-k2b-title-host` on that element, titles that mount or leave in
91
+ * the browser keep the attribute current.
82
92
  */
83
93
  export function PageTrailProvider(props) {
84
94
  return _$createComponent(PageTrailContext.Provider, {
85
95
  value: () => props.trail,
86
96
  get children() {
87
- return [_$createComponent(Show, {
88
- get when() {
89
- return props.trail;
90
- },
91
- children: trail => (() => {
92
- var _el$ = _$getNextElement(_tmpl$);
93
- _$effect(() => _$setProperty(_el$, "innerHTML", serializeTrail(trail())));
94
- return _el$;
95
- })()
96
- }), _$memo(() => props.children)];
97
+ return _$createComponent(OwnTitleContext.Provider, {
98
+ value: () => props.onOwnTitle?.(),
99
+ get children() {
100
+ return [_$createComponent(Show, {
101
+ get when() {
102
+ return props.trail;
103
+ },
104
+ children: trail => (() => {
105
+ var _el$ = _$getNextElement(_tmpl$);
106
+ _$effect(() => _$setProperty(_el$, "innerHTML", serializeTrail(trail())));
107
+ return _el$;
108
+ })()
109
+ }), _$memo(() => props.children)];
110
+ }
111
+ });
97
112
  }
98
113
  });
99
114
  }
@@ -121,6 +136,7 @@ export function PageTitle(props) {
121
136
  const messages = useWorkspaceMessages();
122
137
  // A title rendered on the server from the host's trail follows the host's later trails (see `followPageTrail`).
123
138
  const host = isServer ? useContext(PageTrailContext) : undefined;
139
+ if (isServer && !props.fallback) useContext(OwnTitleContext)?.();
124
140
  const menuId = `k2b-page-title-menu-${createUniqueId()}`;
125
141
  const ancestors = () => props.trail.ancestors;
126
142
  const parent = () => ancestors().at(-1);
@@ -128,6 +144,13 @@ export function PageTitle(props) {
128
144
  // A host may control every title in the document at once; a title outside
129
145
  // one still collapses its path.
130
146
  onMount(() => {
147
+ // A title that the page shows after loading, such as one a client-side step brings, marks its host as well.
148
+ // Without a host that marks its page, the `k2b-ui` root stands for it.
149
+ const titleHost = props.fallback ? null : root?.closest(`[${OWN_TITLE_HOST}]`) ?? root?.closest(".k2b-ui");
150
+ if (titleHost) {
151
+ syncOwnTitle(titleHost);
152
+ onCleanup(() => queueMicrotask(() => syncOwnTitle(titleHost)));
153
+ }
131
154
  if (!root || root.closest(`[${PAGE_TITLE_CONTROLLER_ATTRIBUTE}]`)) return;
132
155
  onCleanup(installPageTitleController(root));
133
156
  });