@k2b/ui 0.11.0 → 0.13.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 (139) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/content/FileView.js +203 -136
  3. package/dist/browser/content/FileView.js.map +1 -1
  4. package/dist/browser/content/PdfPreview.js +5 -2
  5. package/dist/browser/content/PdfPreview.js.map +1 -1
  6. package/dist/browser/content/markdown-title.js +52 -0
  7. package/dist/browser/content/markdown-title.js.map +1 -0
  8. package/dist/browser/feedback/InstallGuide.js +267 -0
  9. package/dist/browser/feedback/InstallGuide.js.map +1 -0
  10. package/dist/browser/feedback/announce.js +50 -0
  11. package/dist/browser/feedback/announce.js.map +1 -0
  12. package/dist/browser/feedback/dialog-core.js +82 -1
  13. package/dist/browser/feedback/dialog-core.js.map +1 -1
  14. package/dist/browser/feedback/index.js +4 -0
  15. package/dist/browser/feedback/index.js.map +1 -1
  16. package/dist/browser/feedback/install.js +99 -0
  17. package/dist/browser/feedback/install.js.map +1 -0
  18. package/dist/browser/feedback/prompts.js +29 -20
  19. package/dist/browser/feedback/prompts.js.map +1 -1
  20. package/dist/browser/feedback/theme-color.js +15 -0
  21. package/dist/browser/feedback/theme-color.js.map +1 -0
  22. package/dist/browser/feedback/toast.js +329 -122
  23. package/dist/browser/feedback/toast.js.map +1 -1
  24. package/dist/browser/inputs/DatePicker.js +263 -222
  25. package/dist/browser/inputs/DatePicker.js.map +1 -1
  26. package/dist/browser/inputs/MultiSelectInput.js +139 -105
  27. package/dist/browser/inputs/MultiSelectInput.js.map +1 -1
  28. package/dist/browser/inputs/NumberInput.js +209 -122
  29. package/dist/browser/inputs/NumberInput.js.map +1 -1
  30. package/dist/browser/inputs/QrScanner.js +164 -0
  31. package/dist/browser/inputs/QrScanner.js.map +1 -0
  32. package/dist/browser/inputs/Select.js +184 -92
  33. package/dist/browser/inputs/Select.js.map +1 -1
  34. package/dist/browser/inputs/index.js +1 -0
  35. package/dist/browser/inputs/index.js.map +1 -1
  36. package/dist/browser/intl/messages.js +90 -2
  37. package/dist/browser/intl/messages.js.map +1 -1
  38. package/dist/browser/layout/DetailPanel.js +2 -1
  39. package/dist/browser/layout/DetailPanel.js.map +1 -1
  40. package/dist/browser/layout/Discussion.js +2 -1
  41. package/dist/browser/layout/Discussion.js.map +1 -1
  42. package/dist/browser/layout/MobileShell.js +242 -0
  43. package/dist/browser/layout/MobileShell.js.map +1 -0
  44. package/dist/browser/layout/Navigation.js +1 -0
  45. package/dist/browser/layout/Navigation.js.map +1 -1
  46. package/dist/browser/layout/PanelDialog.js +1 -0
  47. package/dist/browser/layout/PanelDialog.js.map +1 -1
  48. package/dist/browser/layout/Settings.js +2 -2
  49. package/dist/browser/layout/Settings.js.map +1 -1
  50. package/dist/browser/layout/TabBar.js +65 -0
  51. package/dist/browser/layout/TabBar.js.map +1 -0
  52. package/dist/browser/layout/index.js +2 -0
  53. package/dist/browser/layout/index.js.map +1 -1
  54. package/dist/browser/surfaces/NoticeCard.js +55 -60
  55. package/dist/browser/surfaces/NoticeCard.js.map +1 -1
  56. package/dist/browser/surfaces/index.js +1 -1
  57. package/dist/browser/surfaces/index.js.map +1 -1
  58. package/dist/ssr/content/FileView.js +141 -81
  59. package/dist/ssr/content/FileView.js.map +1 -1
  60. package/dist/ssr/content/PdfPreview.js +4 -2
  61. package/dist/ssr/content/PdfPreview.js.map +1 -1
  62. package/dist/ssr/content/markdown-title.js +52 -0
  63. package/dist/ssr/content/markdown-title.js.map +1 -0
  64. package/dist/ssr/feedback/InstallGuide.js +201 -0
  65. package/dist/ssr/feedback/InstallGuide.js.map +1 -0
  66. package/dist/ssr/feedback/announce.js +50 -0
  67. package/dist/ssr/feedback/announce.js.map +1 -0
  68. package/dist/ssr/feedback/dialog-core.js +82 -1
  69. package/dist/ssr/feedback/dialog-core.js.map +1 -1
  70. package/dist/ssr/feedback/index.js +4 -0
  71. package/dist/ssr/feedback/index.js.map +1 -1
  72. package/dist/ssr/feedback/install.js +99 -0
  73. package/dist/ssr/feedback/install.js.map +1 -0
  74. package/dist/ssr/feedback/prompts.js +12 -2
  75. package/dist/ssr/feedback/prompts.js.map +1 -1
  76. package/dist/ssr/feedback/theme-color.js +15 -0
  77. package/dist/ssr/feedback/theme-color.js.map +1 -0
  78. package/dist/ssr/feedback/toast.js +329 -122
  79. package/dist/ssr/feedback/toast.js.map +1 -1
  80. package/dist/ssr/inputs/DatePicker.js +78 -52
  81. package/dist/ssr/inputs/DatePicker.js.map +1 -1
  82. package/dist/ssr/inputs/MultiSelectInput.js +52 -32
  83. package/dist/ssr/inputs/MultiSelectInput.js.map +1 -1
  84. package/dist/ssr/inputs/NumberInput.js +93 -40
  85. package/dist/ssr/inputs/NumberInput.js.map +1 -1
  86. package/dist/ssr/inputs/QrScanner.js +128 -0
  87. package/dist/ssr/inputs/QrScanner.js.map +1 -0
  88. package/dist/ssr/inputs/Select.js +108 -29
  89. package/dist/ssr/inputs/Select.js.map +1 -1
  90. package/dist/ssr/inputs/index.js +1 -0
  91. package/dist/ssr/inputs/index.js.map +1 -1
  92. package/dist/ssr/intl/messages.js +90 -2
  93. package/dist/ssr/intl/messages.js.map +1 -1
  94. package/dist/ssr/layout/DetailPanel.js +2 -1
  95. package/dist/ssr/layout/DetailPanel.js.map +1 -1
  96. package/dist/ssr/layout/Discussion.js +2 -1
  97. package/dist/ssr/layout/Discussion.js.map +1 -1
  98. package/dist/ssr/layout/MobileShell.js +211 -0
  99. package/dist/ssr/layout/MobileShell.js.map +1 -0
  100. package/dist/ssr/layout/Navigation.js +1 -0
  101. package/dist/ssr/layout/Navigation.js.map +1 -1
  102. package/dist/ssr/layout/PanelDialog.js +1 -0
  103. package/dist/ssr/layout/PanelDialog.js.map +1 -1
  104. package/dist/ssr/layout/Settings.js +2 -2
  105. package/dist/ssr/layout/Settings.js.map +1 -1
  106. package/dist/ssr/layout/TabBar.js +24 -0
  107. package/dist/ssr/layout/TabBar.js.map +1 -0
  108. package/dist/ssr/layout/index.js +2 -0
  109. package/dist/ssr/layout/index.js.map +1 -1
  110. package/dist/ssr/surfaces/NoticeCard.js +21 -30
  111. package/dist/ssr/surfaces/NoticeCard.js.map +1 -1
  112. package/dist/ssr/surfaces/index.js +1 -1
  113. package/dist/ssr/surfaces/index.js.map +1 -1
  114. package/dist/styles.css +1 -1
  115. package/dist/tabler.css +1 -1
  116. package/dist/types/content/FileView.d.ts +11 -1
  117. package/dist/types/content/markdown-title.d.ts +9 -0
  118. package/dist/types/feedback/InstallGuide.d.ts +26 -0
  119. package/dist/types/feedback/announce.d.ts +19 -0
  120. package/dist/types/feedback/dialog-core.d.ts +5 -0
  121. package/dist/types/feedback/index.d.ts +8 -1
  122. package/dist/types/feedback/install.d.ts +32 -0
  123. package/dist/types/feedback/prompts.d.ts +2 -0
  124. package/dist/types/feedback/theme-color.d.ts +6 -0
  125. package/dist/types/feedback/toast.d.ts +7 -0
  126. package/dist/types/inputs/DatePicker.d.ts +11 -2
  127. package/dist/types/inputs/MultiSelectInput.d.ts +5 -1
  128. package/dist/types/inputs/NumberInput.d.ts +14 -0
  129. package/dist/types/inputs/QrScanner.d.ts +20 -0
  130. package/dist/types/inputs/Select.d.ts +10 -0
  131. package/dist/types/inputs/index.d.ts +3 -1
  132. package/dist/types/intl/messages.d.ts +44 -0
  133. package/dist/types/layout/MobileShell.d.ts +40 -0
  134. package/dist/types/layout/Settings.d.ts +2 -2
  135. package/dist/types/layout/TabBar.d.ts +23 -0
  136. package/dist/types/layout/index.d.ts +4 -0
  137. package/dist/types/surfaces/NoticeCard.d.ts +4 -4
  138. package/dist/types/surfaces/index.d.ts +1 -1
  139. package/package.json +3 -2
package/README.md CHANGED
@@ -61,7 +61,7 @@ See the [TextInput guide](https://cloud.k2b.dev/en/ui/input/text) for details.
61
61
  | Inputs | Text fields, selectors, date pickers, file inputs |
62
62
  | Layout | Panels, settings, split panes, floating windows |
63
63
  | Surfaces | Cards, avatars, badges, empty states |
64
- | Feedback | Dialogs, prompts, tooltips, toasts |
64
+ | Feedback | Dialogs, prompts, tooltips, toasts, screen-reader announcements |
65
65
  | Content | Tables, charts, calendars, Markdown, file previews |
66
66
  | Chat | Timeline, messages, composer, context usage |
67
67
  | Widgets | Dashboard summaries, lists, statistics |
@@ -18,7 +18,7 @@ var _tmpl$ = /*#__PURE__*/_$template(`<a><i aria-hidden=true></i><span class=k2b
18
18
  _tmpl$4 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__panel><div class=k2b-content-file-view__preview></div><!$><!/>`),
19
19
  _tmpl$5 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__truncated>`),
20
20
  _tmpl$6 = /*#__PURE__*/_$template(`<span>`),
21
- _tmpl$7 = /*#__PURE__*/_$template(`<div>`),
21
+ _tmpl$7 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__excerpt>`),
22
22
  _tmpl$8 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__document>`),
23
23
  _tmpl$9 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__editor>`),
24
24
  _tmpl$0 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__editor><div class=k2b-markdown-editor data-fill=true role=group><div class="k2b-markdown-editor__toolbar k2b-content-file-view__code-toolbar"><span class=k2b-content-file-view__code-language></span><span class=k2b-markdown-editor__trailing><!$><!/><!$><!/></span></div><div class=k2b-markdown-editor__surface><textarea class=k2b-content-file-view__code-input>`),
@@ -29,22 +29,25 @@ var _tmpl$ = /*#__PURE__*/_$template(`<a><i aria-hidden=true></i><span class=k2b
29
29
  _tmpl$13 = /*#__PURE__*/_$template(`<td>`),
30
30
  _tmpl$14 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__pagination><!$><!/><span><!$><!/>–<!$><!/> / <!$><!/></span><!$><!/>`),
31
31
  _tmpl$15 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__media><img>`),
32
- _tmpl$16 = /*#__PURE__*/_$template(`<object type=application/pdf class=k2b-content-file-view__pdf>`),
33
- _tmpl$17 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__media data-kind=audio><audio controls preload=metadata>`),
34
- _tmpl$18 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__media data-kind=video><video controls preload=metadata playsinline>`),
35
- _tmpl$19 = /*#__PURE__*/_$template(`<a class=k2b-button data-variant=secondary data-size=sm download><i class="ti ti-download"aria-hidden=true></i><!$><!/>`);
32
+ _tmpl$16 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__pdf-viewer><!$><!/><!$><!/>`),
33
+ _tmpl$17 = /*#__PURE__*/_$template(`<object type=application/pdf class=k2b-content-file-view__pdf>`),
34
+ _tmpl$18 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__media data-kind=audio><audio controls preload=metadata>`),
35
+ _tmpl$19 = /*#__PURE__*/_$template(`<div class=k2b-content-file-view__media data-kind=video><video controls preload=metadata playsinline>`),
36
+ _tmpl$20 = /*#__PURE__*/_$template(`<a class=k2b-button data-variant=secondary data-size=sm download><i class="ti ti-download"aria-hidden=true></i><!$><!/>`),
37
+ _tmpl$21 = /*#__PURE__*/_$template(`<div>`);
36
38
  /**
37
39
  * FileView — renders one file through a small renderer registry keyed by
38
40
  * media type / path. Editing is enabled purely by the presence of `save`;
39
41
  * without it every renderer is read-only. IDE-style chrome: editors reuse
40
42
  * the markdown editor's surface (toolbar with an in-toolbar save, Ctrl/Cmd+S),
41
43
  * previews are quiet paper panels with icon-only actions overlaid top-right.
44
+ * A plain preview drops the panel and sits on its host's surface instead.
42
45
  * Custom renderers are passed per instance, which keeps SSR requests and
43
46
  * independently mounted applications isolated.
44
47
  */
45
48
 
46
49
  import { mutation } from "@k2b/stdlib/solid";
47
- import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, onMount, Show, Switch, untrack } from "solid-js";
50
+ import { createContext, createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, onMount, Show, Switch, untrack, useContext } from "solid-js";
48
51
  import { Button } from "../actions/Button";
49
52
  import { prompts } from "../feedback/prompts";
50
53
  import { Tooltip } from "../feedback/Tooltip";
@@ -57,9 +60,15 @@ import CodeDisplay from "./CodeDisplay";
57
60
  import { decodeDelimitedContent, readDelimitedPreferences } from "./delimited-preferences";
58
61
  import { fileViewExtension, getFileViewPreviewKind, parseDelimitedText } from "./file-view-preview";
59
62
  import MarkdownView from "./MarkdownView";
63
+ import { leadingMarkdownTitle } from "./markdown-title";
60
64
  import PdfPreview from "./PdfPreview";
61
65
  import StructuredDataPreview from "./StructuredDataPreview";
62
66
  export { canPreviewFile, getFileViewPreviewKind } from "./file-view-preview";
67
+ /** Instance options that reach the built-in renderers without widening the public renderer props. */
68
+ const FileViewHost = createContext({
69
+ plain: false,
70
+ liftTitle: false
71
+ });
63
72
  const codeLanguage = path => {
64
73
  const extension = fileViewExtension(path);
65
74
  if (extension === "ts" || extension === "mts" || extension === "cts") return "ts";
@@ -274,36 +283,51 @@ function TextExcerpt(props) {
274
283
  const lines = createMemo(() => props.text.replace(/\r\n?/g, "\n").replace(/\n$/, "").split("\n"));
275
284
  const limit = () => props.renderer.previewLines === undefined ? lines().length : Math.max(1, props.renderer.previewLines);
276
285
  const text = () => lines().slice(0, limit()).join("\n");
277
- return [(() => {
278
- var _el$11 = _$getNextElement(_tmpl$7);
279
- _$insert(_el$11, _$createComponent(Show, {
280
- get when() {
281
- return props.markdown;
282
- },
283
- get fallback() {
284
- return _$createComponent(CodeDisplay, {
285
- get code() {
286
- return text();
287
- },
288
- get language() {
289
- return codeLanguage(props.renderer.file.path);
290
- }
291
- });
292
- },
293
- get children() {
294
- return _$createComponent(MarkdownView, {
295
- get markdown() {
296
- return text();
297
- },
298
- get headingScale() {
299
- return props.renderer.headingScale ?? "compact";
300
- }
301
- });
302
- }
303
- }));
304
- _$effect(() => _el$11.classList.toggle("k2b-content-file-view__excerpt", !!(props.renderer.previewLines !== undefined)));
305
- return _el$11;
306
- })(), _$createComponent(MoreLines, {
286
+ const host = useContext(FileViewHost);
287
+ // A plain preview has no code box: the host frames the file and owns its copy action.
288
+ const content = () => _$createComponent(Show, {
289
+ get when() {
290
+ return props.markdown;
291
+ },
292
+ get fallback() {
293
+ return _$createComponent(CodeDisplay, {
294
+ get code() {
295
+ return text();
296
+ },
297
+ get language() {
298
+ return codeLanguage(props.renderer.file.path);
299
+ },
300
+ get copy() {
301
+ return host.plain ? false : undefined;
302
+ }
303
+ });
304
+ },
305
+ get children() {
306
+ return _$createComponent(MarkdownView, {
307
+ get markdown() {
308
+ return text();
309
+ },
310
+ get headingScale() {
311
+ return props.renderer.headingScale ?? "compact";
312
+ }
313
+ });
314
+ }
315
+ });
316
+ // Only an excerpt needs a clipping box; a complete preview keeps its content
317
+ // a direct child of the preview frame, which the frame's styles expect.
318
+ return [_$createComponent(Show, {
319
+ get when() {
320
+ return props.renderer.previewLines !== undefined;
321
+ },
322
+ get fallback() {
323
+ return content();
324
+ },
325
+ get children() {
326
+ var _el$11 = _$getNextElement(_tmpl$7);
327
+ _$insert(_el$11, content);
328
+ return _el$11;
329
+ }
330
+ }), _$createComponent(MoreLines, {
307
331
  get count() {
308
332
  return lines().length - limit();
309
333
  },
@@ -314,7 +338,12 @@ function TextExcerpt(props) {
314
338
  }
315
339
  function MarkdownRenderer(props) {
316
340
  const messages = useUiMessages();
341
+ const host = useContext(FileViewHost);
317
342
  const [editing, setEditing] = createSignal(false);
343
+ const markdown = () => {
344
+ const text = stripFrontmatter(props.editor?.draft() ?? props.content.content);
345
+ return host.liftTitle ? leadingMarkdownTitle(text)?.body ?? text : text;
346
+ };
318
347
  return _$createComponent(Show, {
319
348
  get when() {
320
349
  return _$memo(() => !!props.editor)() && editing();
@@ -342,7 +371,7 @@ function MarkdownRenderer(props) {
342
371
  _$insert(_el$12, _$createComponent(TextExcerpt, {
343
372
  renderer: props,
344
373
  get text() {
345
- return stripFrontmatter(props.editor?.draft() ?? props.content.content);
374
+ return markdown();
346
375
  },
347
376
  markdown: true
348
377
  }));
@@ -895,29 +924,40 @@ function PdfRenderer(props) {
895
924
  }
896
925
  });
897
926
  },
898
- children: pdf => _$createComponent(PdfPreview, {
927
+ children: pdf => // Composed without the standalone heading: the host already names the file, and the frame keeps the
928
+ // file name as its accessible title.
929
+ _$createComponent(PdfPreview, {
899
930
  autoLoad: true,
900
931
  get title() {
901
932
  return props.file.path.slice(props.file.path.lastIndexOf("/") + 1);
902
933
  },
903
934
  request: async () => pdf,
904
- "class": "k2b-content-file-view__pdf"
935
+ children: parts => (() => {
936
+ var _el$63 = _$getNextElement(_tmpl$16),
937
+ _el$64 = _el$63.firstChild,
938
+ [_el$65, _co$12] = _$getNextMarker(_el$64.nextSibling),
939
+ _el$66 = _el$65.nextSibling,
940
+ [_el$67, _co$13] = _$getNextMarker(_el$66.nextSibling);
941
+ _$insert(_el$63, () => parts.actions, _el$65, _co$12);
942
+ _$insert(_el$63, () => parts.content, _el$67, _co$13);
943
+ return _el$63;
944
+ })()
905
945
  })
906
946
  });
907
947
  },
908
948
  children: href => (() => {
909
- var _el$63 = _$getNextElement(_tmpl$16);
949
+ var _el$68 = _$getNextElement(_tmpl$17);
910
950
  _$effect(_p$ => {
911
951
  var _v$9 = href(),
912
952
  _v$0 = props.file.path;
913
- _v$9 !== _p$.e && _$setAttribute(_el$63, "data", _p$.e = _v$9);
914
- _v$0 !== _p$.t && _$setAttribute(_el$63, "aria-label", _p$.t = _v$0);
953
+ _v$9 !== _p$.e && _$setAttribute(_el$68, "data", _p$.e = _v$9);
954
+ _v$0 !== _p$.t && _$setAttribute(_el$68, "aria-label", _p$.t = _v$0);
915
955
  return _p$;
916
956
  }, {
917
957
  e: undefined,
918
958
  t: undefined
919
959
  });
920
- return _el$63;
960
+ return _el$68;
921
961
  })()
922
962
  });
923
963
  }
@@ -928,24 +968,24 @@ function AudioRenderer(props) {
928
968
  return _$createComponent(DownloadAction, props);
929
969
  },
930
970
  get children() {
931
- var _el$64 = _$getNextElement(_tmpl$17),
932
- _el$65 = _el$64.firstChild;
933
- _$addEventListener(_el$65, "error", props.onPreviewError);
934
- _$insert(_el$65, () => messages().audioUnsupported);
971
+ var _el$69 = _$getNextElement(_tmpl$18),
972
+ _el$70 = _el$69.firstChild;
973
+ _$addEventListener(_el$70, "error", props.onPreviewError);
974
+ _$insert(_el$70, () => messages().audioUnsupported);
935
975
  _$effect(_p$ => {
936
976
  var _v$1 = props.crossOrigin,
937
977
  _v$10 = mediaSource(props),
938
978
  _v$11 = props.file.path;
939
- _v$1 !== _p$.e && _$setAttribute(_el$65, "crossorigin", _p$.e = _v$1);
940
- _v$10 !== _p$.t && _$setAttribute(_el$65, "src", _p$.t = _v$10);
941
- _v$11 !== _p$.a && _$setAttribute(_el$65, "aria-label", _p$.a = _v$11);
979
+ _v$1 !== _p$.e && _$setAttribute(_el$70, "crossorigin", _p$.e = _v$1);
980
+ _v$10 !== _p$.t && _$setAttribute(_el$70, "src", _p$.t = _v$10);
981
+ _v$11 !== _p$.a && _$setAttribute(_el$70, "aria-label", _p$.a = _v$11);
942
982
  return _p$;
943
983
  }, {
944
984
  e: undefined,
945
985
  t: undefined,
946
986
  a: undefined
947
987
  });
948
- return _el$64;
988
+ return _el$69;
949
989
  }
950
990
  });
951
991
  }
@@ -956,24 +996,24 @@ function VideoRenderer(props) {
956
996
  return _$createComponent(DownloadAction, props);
957
997
  },
958
998
  get children() {
959
- var _el$66 = _$getNextElement(_tmpl$18),
960
- _el$67 = _el$66.firstChild;
961
- _$addEventListener(_el$67, "error", props.onPreviewError);
962
- _$insert(_el$67, () => messages().videoUnsupported);
999
+ var _el$71 = _$getNextElement(_tmpl$19),
1000
+ _el$72 = _el$71.firstChild;
1001
+ _$addEventListener(_el$72, "error", props.onPreviewError);
1002
+ _$insert(_el$72, () => messages().videoUnsupported);
963
1003
  _$effect(_p$ => {
964
1004
  var _v$12 = props.crossOrigin,
965
1005
  _v$13 = mediaSource(props),
966
1006
  _v$14 = props.file.path;
967
- _v$12 !== _p$.e && _$setAttribute(_el$67, "crossorigin", _p$.e = _v$12);
968
- _v$13 !== _p$.t && _$setAttribute(_el$67, "src", _p$.t = _v$13);
969
- _v$14 !== _p$.a && _$setAttribute(_el$67, "aria-label", _p$.a = _v$14);
1007
+ _v$12 !== _p$.e && _$setAttribute(_el$72, "crossorigin", _p$.e = _v$12);
1008
+ _v$13 !== _p$.t && _$setAttribute(_el$72, "src", _p$.t = _v$13);
1009
+ _v$14 !== _p$.a && _$setAttribute(_el$72, "aria-label", _p$.a = _v$14);
970
1010
  return _p$;
971
1011
  }, {
972
1012
  e: undefined,
973
1013
  t: undefined,
974
1014
  a: undefined
975
1015
  });
976
- return _el$66;
1016
+ return _el$71;
977
1017
  }
978
1018
  });
979
1019
  }
@@ -993,13 +1033,13 @@ function BinaryRenderer(props) {
993
1033
  return props.downloadHref;
994
1034
  },
995
1035
  children: href => (() => {
996
- var _el$68 = _$getNextElement(_tmpl$19),
997
- _el$69 = _el$68.firstChild,
998
- _el$70 = _el$69.nextSibling,
999
- [_el$71, _co$12] = _$getNextMarker(_el$70.nextSibling);
1000
- _$insert(_el$68, () => messages().download, _el$71, _co$12);
1001
- _$effect(() => _$setAttribute(_el$68, "href", href()));
1002
- return _el$68;
1036
+ var _el$73 = _$getNextElement(_tmpl$20),
1037
+ _el$74 = _el$73.firstChild,
1038
+ _el$75 = _el$74.nextSibling,
1039
+ [_el$76, _co$14] = _$getNextMarker(_el$75.nextSibling);
1040
+ _$insert(_el$73, () => messages().download, _el$76, _co$14);
1041
+ _$effect(() => _$setAttribute(_el$73, "href", href()));
1042
+ return _el$73;
1003
1043
  })()
1004
1044
  });
1005
1045
  }
@@ -1128,7 +1168,8 @@ export default function FileView(props) {
1128
1168
  });
1129
1169
  if (unregister) onCleanup(unregister);
1130
1170
  });
1131
- const resolvedContent = createMemo(() => browserPreviewContent() ?? content());
1171
+ // An errored resource throws when read; the error state below shows it instead.
1172
+ const resolvedContent = createMemo(() => browserPreviewContent() ?? (content.error ? undefined : content()));
1132
1173
  const renderer = createMemo(() => {
1133
1174
  const loaded = resolvedContent();
1134
1175
  if (!loaded) return null;
@@ -1151,96 +1192,122 @@ export default function FileView(props) {
1151
1192
  saving: saveMutation.loading,
1152
1193
  save
1153
1194
  } : null;
1195
+
1196
+ // Undefined while loading; the host keeps its own placeholder until the shown file decides.
1197
+ const documentTitle = createMemo(() => {
1198
+ if (!props.onDocumentTitle) return undefined;
1199
+ if (content.error) return null;
1200
+ const loaded = resolvedContent();
1201
+ if (!loaded) return undefined;
1202
+ if (renderer()?.component !== MarkdownRenderer) return null;
1203
+ return leadingMarkdownTitle(stripFrontmatter(editor()?.draft() ?? loaded.content))?.title ?? null;
1204
+ });
1205
+ createEffect(() => {
1206
+ const title = documentTitle();
1207
+ if (title !== undefined) props.onDocumentTitle?.(title);
1208
+ });
1154
1209
  return (() => {
1155
- var _el$72 = _$getNextElement(_tmpl$7);
1156
- _$insert(_el$72, _$createComponent(Switch, {
1210
+ var _el$77 = _$getNextElement(_tmpl$21);
1211
+ _$insert(_el$77, _$createComponent(FileViewHost.Provider, {
1212
+ value: {
1213
+ get plain() {
1214
+ return props.variant === "plain";
1215
+ },
1216
+ get liftTitle() {
1217
+ return Boolean(props.onDocumentTitle);
1218
+ }
1219
+ },
1157
1220
  get children() {
1158
- return [_$createComponent(Match, {
1159
- get when() {
1160
- return _$memo(() => !!content.loading)() && content() === undefined;
1161
- },
1162
- get children() {
1163
- return _$createComponent(Placeholder, {
1164
- icon: "ti ti-loader-2",
1165
- get title() {
1166
- return messages().loading;
1167
- }
1168
- });
1169
- }
1170
- }), _$createComponent(Match, {
1171
- get when() {
1172
- return content.error;
1173
- },
1221
+ return _$createComponent(Switch, {
1174
1222
  get children() {
1175
- return _$createComponent(Placeholder, {
1176
- icon: "ti ti-alert-circle",
1177
- get title() {
1178
- return messages().failedLoadFile;
1223
+ return [_$createComponent(Match, {
1224
+ get when() {
1225
+ return _$memo(() => !!content.loading)() && content() === undefined;
1179
1226
  },
1180
- get description() {
1181
- return String(content.error?.message ?? "");
1227
+ get children() {
1228
+ return _$createComponent(Placeholder, {
1229
+ icon: "ti ti-loader-2",
1230
+ get title() {
1231
+ return messages().loading;
1232
+ }
1233
+ });
1182
1234
  }
1183
- });
1184
- }
1185
- }), _$createComponent(Match, {
1186
- get when() {
1187
- return _$memo(() => !!resolvedContent())() && renderer();
1188
- },
1189
- children: active => {
1190
- const Renderer = active().component;
1191
- return _$createComponent(Renderer, {
1192
- get previewLines() {
1193
- return props.previewLines;
1194
- },
1195
- get onExpandPreview() {
1196
- return props.onExpandPreview;
1197
- },
1198
- get headingScale() {
1199
- return props.headingScale;
1200
- },
1201
- get previewPreferencesKey() {
1202
- return props.previewPreferencesKey;
1235
+ }), _$createComponent(Match, {
1236
+ get when() {
1237
+ return content.error;
1203
1238
  },
1204
- get file() {
1205
- return props.file;
1206
- },
1207
- get content() {
1208
- return resolvedContent();
1209
- },
1210
- get previewHref() {
1211
- return nativePreviewHref();
1212
- },
1213
- get crossOrigin() {
1214
- return props.crossOrigin;
1215
- },
1216
- get onPreviewError() {
1217
- return props.onPreviewError;
1218
- },
1219
- get downloadHref() {
1220
- return props.downloadHref ?? null;
1239
+ get children() {
1240
+ return _$createComponent(Placeholder, {
1241
+ icon: "ti ti-alert-circle",
1242
+ get title() {
1243
+ return messages().failedLoadFile;
1244
+ },
1245
+ get description() {
1246
+ return String(content.error?.message ?? "");
1247
+ }
1248
+ });
1249
+ }
1250
+ }), _$createComponent(Match, {
1251
+ get when() {
1252
+ return _$memo(() => !!resolvedContent())() && renderer();
1221
1253
  },
1222
- get editor() {
1223
- return editor();
1254
+ children: active => {
1255
+ const Renderer = active().component;
1256
+ return _$createComponent(Renderer, {
1257
+ get previewLines() {
1258
+ return props.previewLines;
1259
+ },
1260
+ get onExpandPreview() {
1261
+ return props.onExpandPreview;
1262
+ },
1263
+ get headingScale() {
1264
+ return props.headingScale;
1265
+ },
1266
+ get previewPreferencesKey() {
1267
+ return props.previewPreferencesKey;
1268
+ },
1269
+ get file() {
1270
+ return props.file;
1271
+ },
1272
+ get content() {
1273
+ return resolvedContent();
1274
+ },
1275
+ get previewHref() {
1276
+ return nativePreviewHref();
1277
+ },
1278
+ get crossOrigin() {
1279
+ return props.crossOrigin;
1280
+ },
1281
+ get onPreviewError() {
1282
+ return props.onPreviewError;
1283
+ },
1284
+ get downloadHref() {
1285
+ return props.downloadHref ?? null;
1286
+ },
1287
+ get editor() {
1288
+ return editor();
1289
+ }
1290
+ });
1224
1291
  }
1225
- });
1292
+ })];
1226
1293
  }
1227
- })];
1294
+ });
1228
1295
  }
1229
1296
  }));
1230
1297
  _$effect(_p$ => {
1231
1298
  var _v$15 = `k2b-content-file-view ${props.class ?? ""}`,
1232
1299
  _v$16 = props.variant,
1233
1300
  _v$17 = props.previewLines !== undefined ? "true" : undefined;
1234
- _v$15 !== _p$.e && _$className(_el$72, _p$.e = _v$15);
1235
- _v$16 !== _p$.t && _$setAttribute(_el$72, "data-variant", _p$.t = _v$16);
1236
- _v$17 !== _p$.a && _$setAttribute(_el$72, "data-excerpt", _p$.a = _v$17);
1301
+ _v$15 !== _p$.e && _$className(_el$77, _p$.e = _v$15);
1302
+ _v$16 !== _p$.t && _$setAttribute(_el$77, "data-variant", _p$.t = _v$16);
1303
+ _v$17 !== _p$.a && _$setAttribute(_el$77, "data-excerpt", _p$.a = _v$17);
1237
1304
  return _p$;
1238
1305
  }, {
1239
1306
  e: undefined,
1240
1307
  t: undefined,
1241
1308
  a: undefined
1242
1309
  });
1243
- return _el$72;
1310
+ return _el$77;
1244
1311
  })();
1245
1312
  }
1246
1313
  _$delegateEvents(["keydown", "input"]);