@parallelworks/ui 0.51.0 → 0.53.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 (250) hide show
  1. package/README.md +197 -17
  2. package/dist/ai/agent/toolStrings.d.ts +2 -0
  3. package/dist/ai/agent/toolStrings.js +20 -18
  4. package/dist/ai/ai.css +17 -3
  5. package/dist/ai/components/AllocationSelector.js +5 -1
  6. package/dist/ai/components/AttachmentManager.js +51 -42
  7. package/dist/ai/components/AttachmentUpload.d.ts +4 -2
  8. package/dist/ai/components/AttachmentUpload.js +9 -9
  9. package/dist/ai/components/BlockedGroupBanner.js +8 -14
  10. package/dist/ai/components/BranchNavigator.js +11 -12
  11. package/dist/ai/components/ChatEmptyState.js +12 -19
  12. package/dist/ai/components/ChatInput.d.ts +4 -3
  13. package/dist/ai/components/ChatInput.js +27 -13
  14. package/dist/ai/components/ChatMessage.js +45 -121
  15. package/dist/ai/components/ChatMessageList.d.ts +5 -2
  16. package/dist/ai/components/ChatMessageList.js +89 -51
  17. package/dist/ai/components/ChatSidebar.js +11 -7
  18. package/dist/ai/components/ChatThread.js +1 -13
  19. package/dist/ai/components/DragOverlay.js +3 -3
  20. package/dist/ai/components/MessageAttachments.d.ts +10 -0
  21. package/dist/ai/components/MessageAttachments.js +78 -0
  22. package/dist/ai/components/ModelSelector.js +3 -3
  23. package/dist/ai/components/PastedText.js +2 -2
  24. package/dist/ai/components/ProviderIssueBanner.js +19 -23
  25. package/dist/ai/components/ShareDialog.js +2 -2
  26. package/dist/ai/components/agent/ActivityLine.d.ts +0 -2
  27. package/dist/ai/components/agent/ActivityLine.js +8 -51
  28. package/dist/ai/components/agent/AgentMessageParts.js +114 -53
  29. package/dist/ai/core/ChatProvider.d.ts +16 -5
  30. package/dist/ai/core/ChatProvider.js +41 -8
  31. package/dist/ai/core/config.js +4 -3
  32. package/dist/ai/index.d.ts +1 -1
  33. package/dist/ai/index.js +3 -3
  34. package/dist/ai/strings.d.ts +29 -3
  35. package/dist/ai/strings.js +80 -26
  36. package/dist/ai/types.d.ts +4 -0
  37. package/dist/ai/ui/Notice.d.ts +2 -2
  38. package/dist/ai/ui/Notice.js +29 -62
  39. package/dist/ai/ui/sidebar.d.ts +5 -3
  40. package/dist/ai/ui/sidebar.js +22 -10
  41. package/dist/components/ActionMenu.d.ts +1 -0
  42. package/dist/components/ActionMenu.js +6 -1
  43. package/dist/components/AppLink.d.ts +26 -0
  44. package/dist/components/AppLink.js +51 -0
  45. package/dist/components/Avatar.d.ts +2 -1
  46. package/dist/components/Avatar.js +4 -2
  47. package/dist/components/Button.d.ts +15 -5
  48. package/dist/components/Button.js +47 -40
  49. package/dist/components/Callout.d.ts +31 -7
  50. package/dist/components/Callout.js +50 -19
  51. package/dist/components/Card.d.ts +11 -6
  52. package/dist/components/Card.js +14 -8
  53. package/dist/components/Checkbox.js +2 -1
  54. package/dist/components/ColorInput.js +5 -3
  55. package/dist/components/Combobox.js +49 -19
  56. package/dist/components/ConfirmModal.d.ts +19 -8
  57. package/dist/components/ConfirmModal.js +31 -53
  58. package/dist/components/CopyButton.d.ts +38 -0
  59. package/dist/components/CopyButton.js +68 -0
  60. package/dist/components/CreateModal.js +118 -111
  61. package/dist/components/DatePicker.js +12 -8
  62. package/dist/components/DescriptionList.d.ts +25 -5
  63. package/dist/components/DescriptionList.js +56 -22
  64. package/dist/components/Disclosure.js +1 -1
  65. package/dist/components/Drawer.js +10 -16
  66. package/dist/components/Field.d.ts +16 -5
  67. package/dist/components/Field.js +63 -8
  68. package/dist/components/IconButton.d.ts +14 -5
  69. package/dist/components/IconButton.js +25 -14
  70. package/dist/components/InfoTip.d.ts +12 -1
  71. package/dist/components/InfoTip.js +13 -2
  72. package/dist/components/Input.d.ts +14 -8
  73. package/dist/components/Input.js +46 -31
  74. package/dist/components/Loader.js +1 -1
  75. package/dist/components/MarkdownImpl.d.ts +1 -0
  76. package/dist/components/MarkdownImpl.js +1 -0
  77. package/dist/components/MenuEntries.d.ts +4 -0
  78. package/dist/components/MenuEntries.js +11 -12
  79. package/dist/components/Meter.js +5 -4
  80. package/dist/components/Modal.d.ts +28 -4
  81. package/dist/components/Modal.js +37 -14
  82. package/dist/components/Overview.d.ts +20 -12
  83. package/dist/components/Overview.js +14 -9
  84. package/dist/components/PageHeader.d.ts +47 -65
  85. package/dist/components/PageHeader.js +275 -153
  86. package/dist/components/PageHeaderAction.d.ts +67 -0
  87. package/dist/components/PageHeaderAction.js +107 -0
  88. package/dist/components/Pagination.js +6 -16
  89. package/dist/components/Popover.d.ts +4 -1
  90. package/dist/components/Popover.js +5 -3
  91. package/dist/components/Provider.d.ts +79 -19
  92. package/dist/components/Provider.js +140 -130
  93. package/dist/components/Radio.js +2 -1
  94. package/dist/components/RefreshButton.js +7 -8
  95. package/dist/components/RequiredMark.js +1 -1
  96. package/dist/components/Section.js +1 -1
  97. package/dist/components/Select.d.ts +4 -9
  98. package/dist/components/Select.js +24 -11
  99. package/dist/components/SettingModeOption.d.ts +1 -1
  100. package/dist/components/SettingModeOption.js +7 -1
  101. package/dist/components/SettingsCard.d.ts +5 -3
  102. package/dist/components/SettingsCard.js +27 -20
  103. package/dist/components/Slider.js +1 -1
  104. package/dist/components/StatusBadge.js +2 -2
  105. package/dist/components/Switch.js +5 -4
  106. package/dist/components/Table.d.ts +30 -20
  107. package/dist/components/Table.js +50 -73
  108. package/dist/components/Tabs.d.ts +3 -7
  109. package/dist/components/Tabs.js +6 -10
  110. package/dist/components/ThemeEditor.js +18 -8
  111. package/dist/components/Toast.d.ts +11 -3
  112. package/dist/components/Toast.js +45 -20
  113. package/dist/components/buttonClasses.js +1 -1
  114. package/dist/components/comboboxItems.d.ts +4 -0
  115. package/dist/components/comboboxItems.js +6 -1
  116. package/dist/components/dismiss.d.ts +12 -0
  117. package/dist/components/dismiss.js +19 -0
  118. package/dist/components/fieldBox.d.ts +4 -0
  119. package/dist/components/fieldBox.js +7 -3
  120. package/dist/components/floating.d.ts +3 -3
  121. package/dist/components/floating.js +1 -1
  122. package/dist/components/focus.d.ts +5 -0
  123. package/dist/components/focus.js +21 -1
  124. package/dist/components/markdownCodeBreaks.js +3 -4
  125. package/dist/components/pageHeaderStore.d.ts +41 -0
  126. package/dist/components/pageHeaderStore.js +67 -0
  127. package/dist/components/pathBreaks.d.ts +8 -0
  128. package/dist/components/pathBreaks.js +28 -0
  129. package/dist/components/segmented.js +1 -1
  130. package/dist/components/shortcutKey.d.ts +2 -1
  131. package/dist/components/shortcutKey.js +19 -2
  132. package/dist/components/splitter.js +1 -1
  133. package/dist/components/useCssIsDark.js +40 -18
  134. package/dist/components/useTheme.js +9 -10
  135. package/dist/dev.d.ts +9 -0
  136. package/dist/dev.js +14 -0
  137. package/dist/editor/Editor.d.ts +3 -2
  138. package/dist/editor/Editor.js +24 -5
  139. package/dist/editor/EditorField.d.ts +2 -1
  140. package/dist/editor/EditorField.js +11 -29
  141. package/dist/editor/FormCodePanel.d.ts +7 -3
  142. package/dist/editor/FormCodePanel.js +14 -7
  143. package/dist/editor/Monaco.d.ts +22 -6
  144. package/dist/editor/Monaco.js +177 -113
  145. package/dist/editor/editorModel.d.ts +1 -1
  146. package/dist/editor/editorModel.js +1 -1
  147. package/dist/editor/index.d.ts +6 -5
  148. package/dist/editor/index.js +5 -4
  149. package/dist/editor/languages.d.ts +24 -0
  150. package/dist/editor/languages.js +31 -0
  151. package/dist/editor/monacoFeatures.d.ts +7 -0
  152. package/dist/editor/monacoFeatures.js +19 -0
  153. package/dist/editor/themes.d.ts +8 -5
  154. package/dist/editor/themes.js +65 -32
  155. package/dist/editor/workers.d.ts +3 -3
  156. package/dist/editor/workers.js +8 -4
  157. package/dist/editor/yaml.d.ts +6 -2
  158. package/dist/editor/yaml.js +11 -2
  159. package/dist/editor/yamlLanguage.js +55 -38
  160. package/dist/file-explorer/FileExplorer.d.ts +18 -4
  161. package/dist/file-explorer/FileExplorer.js +204 -142
  162. package/dist/file-explorer/FileExplorerContext.js +1 -1
  163. package/dist/file-explorer/FilePreview/StreamedBadge.js +3 -1
  164. package/dist/file-explorer/FilePreview/Viewer.js +14 -10
  165. package/dist/file-explorer/FilePreview/index.js +4 -4
  166. package/dist/file-explorer/FileRow.d.ts +6 -2
  167. package/dist/file-explorer/FileRow.js +35 -17
  168. package/dist/file-explorer/FileRowsWindow.d.ts +2 -1
  169. package/dist/file-explorer/FileRowsWindow.js +36 -4
  170. package/dist/file-explorer/FileTable.d.ts +3 -1
  171. package/dist/file-explorer/FileTable.js +6 -3
  172. package/dist/file-explorer/TreeView.d.ts +5 -1
  173. package/dist/file-explorer/TreeView.js +86 -39
  174. package/dist/file-explorer/helpers.d.ts +2 -1
  175. package/dist/file-explorer/helpers.js +24 -23
  176. package/dist/file-explorer/lib/utils.d.ts +3 -0
  177. package/dist/file-explorer/lib/utils.js +6 -1
  178. package/dist/file-explorer/toasts.js +27 -19
  179. package/dist/file-explorer/treeRows.d.ts +6 -0
  180. package/dist/file-explorer/treeRows.js +19 -1
  181. package/dist/file-explorer/useNearEnd.js +3 -6
  182. package/dist/form/Fields/CheckboxGroupField.js +12 -5
  183. package/dist/form/Fields/DurationField.js +1 -1
  184. package/dist/form/Fields/LabelField.js +1 -1
  185. package/dist/form/Fields/ObjectField.d.ts +1 -1
  186. package/dist/form/Fields/ObjectField.js +3 -2
  187. package/dist/form/Fields/StringField.js +16 -29
  188. package/dist/form/Fields/TextAreaField.js +3 -4
  189. package/dist/form/Form.js +3 -8
  190. package/dist/form/Wizard/WizardNavigation.js +0 -3
  191. package/dist/form/Wizard/utils.js +2 -1
  192. package/dist/form/fieldFrame.d.ts +1 -1
  193. package/dist/form/fieldFrame.js +2 -2
  194. package/dist/form/types/fieldComponentTypes.d.ts +2 -1
  195. package/dist/graph/AnnotationBanner.d.ts +1 -2
  196. package/dist/graph/AnnotationBanner.js +30 -32
  197. package/dist/graph/Connectors.js +4 -1
  198. package/dist/graph/DependencyGraph.js +2 -2
  199. package/dist/graph/GraphEditor.js +11 -9
  200. package/dist/graph/GraphEditorDialogs.js +1 -1
  201. package/dist/graph/GraphNode.js +7 -4
  202. package/dist/graph/InputDialog.js +1 -1
  203. package/dist/graph/InputsEditor.js +14 -11
  204. package/dist/graph/MatrixGroup.js +9 -3
  205. package/dist/graph/Reveal.js +18 -10
  206. package/dist/graph/ShortcutsButton.d.ts +11 -2
  207. package/dist/graph/ShortcutsButton.js +14 -3
  208. package/dist/graph/TreeView.js +35 -18
  209. package/dist/graph/WorkflowSettingsDialog.js +1 -1
  210. package/dist/graph/editorFields.js +1 -1
  211. package/dist/graph/editorRows.js +16 -10
  212. package/dist/graph/editorStrings.d.ts +7 -0
  213. package/dist/graph/editorStrings.js +8 -1
  214. package/dist/graph/inputRefs.js +1 -1
  215. package/dist/graph/settingsViews.js +11 -18
  216. package/dist/graph/stepWith.js +1 -1
  217. package/dist/icons.d.ts +2 -0
  218. package/dist/icons.js +4 -2
  219. package/dist/index.d.ts +12 -12
  220. package/dist/index.js +16 -16
  221. package/dist/list/ActionToolbar.d.ts +42 -6
  222. package/dist/list/ActionToolbar.js +298 -101
  223. package/dist/list/ListPage.d.ts +15 -23
  224. package/dist/list/ListPage.js +42 -43
  225. package/dist/list/ListTable.d.ts +37 -7
  226. package/dist/list/ListTable.js +52 -12
  227. package/dist/list/ListViewControls.js +1 -1
  228. package/dist/list/UserHoverCard.js +7 -15
  229. package/dist/list/index.d.ts +2 -2
  230. package/dist/list/index.js +2 -2
  231. package/dist/list/listSearch.js +12 -10
  232. package/dist/list/listView.d.ts +10 -1
  233. package/dist/list/listView.js +15 -1
  234. package/dist/logviewer/LogViewer.d.ts +4 -2
  235. package/dist/logviewer/LogViewer.js +87 -69
  236. package/dist/logviewer/logviewer.css +3 -2
  237. package/dist/reducedMotion.d.ts +5 -0
  238. package/dist/reducedMotion.js +20 -0
  239. package/dist/safeUrl.d.ts +1 -1
  240. package/dist/safeUrl.js +4 -2
  241. package/dist/styles.css +1 -1
  242. package/package.json +5 -2
  243. package/dist/components/Breadcrumbs.d.ts +0 -37
  244. package/dist/components/Breadcrumbs.js +0 -90
  245. package/dist/components/CopyToClipboard.d.ts +0 -17
  246. package/dist/components/CopyToClipboard.js +0 -66
  247. package/dist/components/Sort.d.ts +0 -9
  248. package/dist/components/Sort.js +0 -42
  249. package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +0 -6
  250. package/dist/file-explorer/FilePreview/CodePreviewEditor.js +0 -64
@@ -1,8 +1,8 @@
1
1
  import { cx } from "../../cx.js";
2
2
  import { ChevronLeftIcon, ChevronRightIcon, CloseIcon, DownloadIcon, FileIcon, LinkIcon, ZoomInIcon, ZoomOutIcon } from "../../icons.js";
3
3
  import { usePersistedState, useStrings } from "../../components/Provider.js";
4
- import { Modal } from "../../components/Modal.js";
5
4
  import { IconButton } from "../../components/IconButton.js";
5
+ import { Modal } from "../../components/Modal.js";
6
6
  import { formatBytes } from "../../format.js";
7
7
  import { SegmentedControl } from "../../components/SegmentedControl.js";
8
8
  import { getPreviewKind } from "./previewType.js";
@@ -20,7 +20,7 @@ function isDocumentView(kind) {
20
20
  function FilePreview({ open, node, siblings, storage, getPresignedUrl, onNavigate, onClose, onDownload, onCopyUri }) {
21
21
  const t = useStrings().fileExplorer;
22
22
  const { common } = useStrings();
23
- const [style, setStyle] = usePersistedState("fileExplorer.previewStyle", "quicklook");
23
+ const [style, setStyle] = usePersistedState("fileExplorer.previewStyle", "compact");
24
24
  const [zoom, setZoom] = useState(100);
25
25
  const { url, loading: urlLoading, error: urlError } = usePresignedUrl(node, getPresignedUrl, {
26
26
  enabled: open,
@@ -67,8 +67,8 @@ function FilePreview({ open, node, siblings, storage, getPresignedUrl, onNavigat
67
67
  value: style,
68
68
  onValueChange: setStyle,
69
69
  options: [{
70
- value: "quicklook",
71
- label: t.preview.quickLook
70
+ value: "compact",
71
+ label: t.preview.compact
72
72
  }, {
73
73
  value: "overlay",
74
74
  label: t.preview.fullWindow
@@ -16,6 +16,8 @@ interface FileRowProps {
16
16
  rowIndex: number;
17
17
  /** The keyboard cursor row. Focus stays on the container, so nothing else marks it. */
18
18
  active: boolean;
19
+ /** Makes the row a link to the node. */
20
+ href?: string | undefined;
19
21
  checked: boolean;
20
22
  canPreview: boolean;
21
23
  getItems: () => MenuEntry[];
@@ -24,13 +26,15 @@ interface FileRowProps {
24
26
  onToggleCheck: (node: TreeNode, checked: boolean) => void;
25
27
  onPreview: (node: TreeNode) => void;
26
28
  }
27
- export declare function UserRow({ node, icon, storageCount, getItems, openMenu, onOpen, }: {
29
+ export declare function UserRow({ node, icon, href, storageCount, getItems, openMenu, onOpen, }: {
28
30
  node: TreeNode;
29
31
  icon: ReactNode;
32
+ /** Makes the row a link to the user. */
33
+ href?: string | undefined;
30
34
  storageCount: number;
31
35
  getItems: () => MenuEntry[];
32
36
  openMenu: OpenMenu;
33
37
  onOpen: (node: TreeNode) => void;
34
38
  }): import("react").JSX.Element;
35
- export declare function FileRow({ node, icon, rowId, rowIndex, active, checked, canPreview, getItems, openMenu, onOpen, onToggleCheck, onPreview, }: FileRowProps): import("react").JSX.Element;
39
+ export declare function FileRow({ node, icon, rowId, rowIndex, active, href, checked, canPreview, getItems, openMenu, onOpen, onToggleCheck, onPreview, }: FileRowProps): import("react").JSX.Element;
36
40
  export {};
@@ -2,10 +2,11 @@ import { cx } from "../cx.js";
2
2
  import { PreviewIcon } from "../icons.js";
3
3
  import { Tooltip } from "../components/Tooltip.js";
4
4
  import { useStrings } from "../components/Provider.js";
5
+ import { AppLink, isPlainClick } from "../components/AppLink.js";
5
6
  import { formatBytes } from "../format.js";
6
7
  import { RelativeTime } from "../components/relativeTime.js";
7
8
  import { Table } from "../components/Table.js";
8
- import { RowSelectCheckbox } from "../list/ListTable.js";
9
+ import { RowSelectCheckbox, STRETCHED_LINK } from "../list/ListTable.js";
9
10
  import { MoreButton, rowContextMenuProps, useRowMenuActive } from "../list/rowMenu.js";
10
11
  import { jsx, jsxs } from "react/jsx-runtime";
11
12
  import { DateTime } from "luxon";
@@ -20,16 +21,31 @@ function fileRowHeight(node) {
20
21
  function isInteractiveTarget(e) {
21
22
  return !!e.target.closest("a,button,input,label");
22
23
  }
23
- function UserRow({ node, icon, storageCount, getItems, openMenu, onOpen }) {
24
+ /** Opens the row on a click its controls don't take. Beside a link, which
25
+ * covers the row, that is only a click on a gap the link leaves, and a
26
+ * modified one there asks for a new tab, which only the link can give. */
27
+ function rowClickHandler(onOpen, href) {
28
+ return (e) => {
29
+ if (isInteractiveTarget(e) || href && !isPlainClick(e)) return;
30
+ onOpen();
31
+ };
32
+ }
33
+ /** A row's name: with an href, the link that covers the row, so the whole row
34
+ * opens in a new tab, shows its URL and reads as a link. */
35
+ function RowName({ name, href }) {
36
+ return href ? /* @__PURE__ */ jsx(AppLink, {
37
+ href,
38
+ "data-row-link": true,
39
+ className: STRETCHED_LINK,
40
+ children: name
41
+ }) : name;
42
+ }
43
+ function UserRow({ node, icon, href, storageCount, getItems, openMenu, onOpen }) {
24
44
  const t = useStrings().fileExplorer;
25
45
  const { active: menuActive, openMenu: openRowMenu } = useRowMenuActive(openMenu);
26
- const handleRowClick = (e) => {
27
- if (isInteractiveTarget(e)) return;
28
- onOpen(node);
29
- };
30
46
  return /* @__PURE__ */ jsxs("tr", {
31
- className: cx("group cursor-pointer", menuActive ? "theme-hover" : "hover:theme-hover"),
32
- onClick: handleRowClick,
47
+ className: cx("group cursor-pointer", menuActive ? "theme-hover" : "hover:theme-hover", href && "relative [&_:is(a,button,input,label,[role=checkbox]):not([data-row-link])]:relative [&_:is(a,button,input,label,[role=checkbox]):not([data-row-link])]:z-[1]"),
48
+ onClick: rowClickHandler(() => onOpen(node), href),
33
49
  ...rowContextMenuProps(openRowMenu, getItems),
34
50
  children: [
35
51
  /* @__PURE__ */ jsx(Table.Item, {
@@ -41,7 +57,10 @@ function UserRow({ node, icon, storageCount, getItems, openMenu, onOpen }) {
41
57
  children: icon
42
58
  }), /* @__PURE__ */ jsx("span", {
43
59
  className: "truncate text-[13px] font-medium text-(--theme-app)",
44
- children: node.displayName || node.name
60
+ children: /* @__PURE__ */ jsx(RowName, {
61
+ name: node.displayName || node.name,
62
+ href
63
+ })
45
64
  })]
46
65
  })
47
66
  }),
@@ -66,22 +85,18 @@ function UserRow({ node, icon, storageCount, getItems, openMenu, onOpen }) {
66
85
  ]
67
86
  });
68
87
  }
69
- function FileRow({ node, icon, rowId, rowIndex, active, checked, canPreview, getItems, openMenu, onOpen, onToggleCheck, onPreview }) {
88
+ function FileRow({ node, icon, rowId, rowIndex, active, href, checked, canPreview, getItems, openMenu, onOpen, onToggleCheck, onPreview }) {
70
89
  const t = useStrings().fileExplorer;
71
90
  const { active: menuActive, openMenu: openRowMenu } = useRowMenuActive(openMenu);
72
91
  const isDir = node.type === "directory";
73
92
  const modifiedDt = node.modified ? DateTime.fromISO(node.modified) : null;
74
93
  const typeLabel = node.root ? node.storageType ?? node.type : node.type;
75
- const handleRowClick = (e) => {
76
- if (isInteractiveTarget(e)) return;
77
- onOpen(node);
78
- };
79
94
  return /* @__PURE__ */ jsxs("tr", {
80
95
  id: rowId,
81
96
  "aria-rowindex": rowIndex,
82
97
  "aria-selected": checked,
83
- className: cx("group", checked ? "bg-blue-500/5" : menuActive ? "theme-hover" : "hover:theme-hover", active && "group-focus-visible/grid:ring-1 group-focus-visible/grid:ring-inset group-focus-visible/grid:ring-(--theme-link)", "cursor-pointer"),
84
- onClick: handleRowClick,
98
+ className: cx("group", checked ? "bg-(--theme-accent-subtle)/60" : menuActive ? "theme-hover" : "hover:theme-hover", active && "group-focus-visible/grid:ring-1 group-focus-visible/grid:ring-inset group-focus-visible/grid:ring-(--theme-link)", "cursor-pointer", href && "relative [&_:is(a,button,input,label,[role=checkbox]):not([data-row-link])]:relative [&_:is(a,button,input,label,[role=checkbox]):not([data-row-link])]:z-[1]"),
99
+ onClick: rowClickHandler(() => onOpen(node), href),
85
100
  style: { height: fileRowHeight(node) },
86
101
  ...rowContextMenuProps(openRowMenu, getItems),
87
102
  children: [
@@ -103,7 +118,10 @@ function FileRow({ node, icon, rowId, rowIndex, active, checked, canPreview, get
103
118
  className: "min-w-0",
104
119
  children: [/* @__PURE__ */ jsx("div", {
105
120
  className: "truncate text-[13px] font-medium leading-tight text-(--theme-app)",
106
- children: node.displayName || node.name
121
+ children: /* @__PURE__ */ jsx(RowName, {
122
+ name: node.displayName || node.name,
123
+ href
124
+ })
107
125
  }), isTwoLineRow(node) && /* @__PURE__ */ jsx("div", {
108
126
  className: "truncate text-xs leading-tight text-(--theme-muted-text-color)",
109
127
  children: node.contentType
@@ -30,8 +30,9 @@ interface FileRowsWindowProps {
30
30
  onOpen: (node: TreeNode) => void;
31
31
  onToggleCheck: (node: TreeNode, checked: boolean) => void;
32
32
  onPreview: (node: TreeNode) => void;
33
+ pathHref?: ((path: string) => string) | undefined;
33
34
  }
34
35
  /** Must stay a single child of `<Table>`: Table re-keys children by traversal
35
36
  * index, so a count that varied with scroll position would remount every row. */
36
- export declare function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeIndex, rowIdPrefix, scrollEl, loadMore, getItems, openMenu, onOpen, onToggleCheck, onPreview, }: FileRowsWindowProps): import("react").JSX.Element;
37
+ export declare function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeIndex, rowIdPrefix, scrollEl, loadMore, getItems, openMenu, onOpen, onToggleCheck, onPreview, pathHref, }: FileRowsWindowProps): import("react").JSX.Element;
37
38
  export {};
@@ -1,7 +1,8 @@
1
1
  import { FileIcon, FolderIcon } from "../icons.js";
2
+ import { isDev } from "../dev.js";
2
3
  import { canPreview } from "./FilePreview/previewType.js";
3
4
  import { getNodeIcon } from "./helpers.js";
4
- import { FileRow } from "./FileRow.js";
5
+ import { FileRow, ROW_HEIGHT_TALL, isTwoLineRow } from "./FileRow.js";
5
6
  import { LoadMoreRow } from "./LoadMoreRow.js";
6
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
8
  import { useEffect } from "react";
@@ -22,7 +23,7 @@ function spacerHeights(items, totalSize, scrollMargin) {
22
23
  }
23
24
  /** Must stay a single child of `<Table>`: Table re-keys children by traversal
24
25
  * index, so a count that varied with scroll position would remount every row. */
25
- function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeIndex, rowIdPrefix, scrollEl, loadMore, getItems, openMenu, onOpen, onToggleCheck, onPreview }) {
26
+ function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeIndex, rowIdPrefix, scrollEl, loadMore, getItems, openMenu, onOpen, onToggleCheck, onPreview, pathHref }) {
26
27
  useNestedScrollerWarning(scrollEl, nodes.length);
27
28
  useRowPitchAudit(scrollEl, nodes, items);
28
29
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -43,6 +44,7 @@ function FileRowsWindow({ items, padTop, padBottom, nodes, checkedItems, activeI
43
44
  rowId: `${rowIdPrefix}-${item.index}`,
44
45
  rowIndex: item.index + 2,
45
46
  active: item.index === activeIndex,
47
+ href: pathHref?.(node.path),
46
48
  node,
47
49
  icon,
48
50
  checked: checkedItems.has(node.path),
@@ -73,13 +75,43 @@ function Spacer({ height }) {
73
75
  /** A nested overflow container stealing the scroll leaves the virtualizer at
74
76
  * scrollTop 0 forever, rendering only the first window. */
75
77
  function useNestedScrollerWarning(scrollEl, count) {
76
- useEffect(() => {}, [scrollEl, count]);
78
+ useEffect(() => {
79
+ if (!isDev() || !scrollEl || count <= 100) return;
80
+ if (scrollEl.scrollHeight <= scrollEl.clientHeight) console.warn("FileRowsWindow: scrollEl is not scrolling — a nested overflow container likely stole it, so only the first window will ever render.");
81
+ }, [scrollEl, count]);
82
+ }
83
+ /** Pitch per kind, from the first adjacent same-kind pair: a mixed pair says
84
+ * nothing about either one. */
85
+ function measurePitches(rows) {
86
+ const pitches = /* @__PURE__ */ new Map();
87
+ rows.forEach((row, i) => {
88
+ const next = rows[i + 1];
89
+ if (next && next.kind === row.kind && !pitches.has(row.kind)) pitches.set(row.kind, next.top - row.top);
90
+ });
91
+ return pitches;
77
92
  }
78
93
  /** The constants must equal the row *pitch*, not the row's own height —
79
94
  * under border-collapse the shared border sits between rows. A 1px error
80
95
  * compounds to 10,000px across a 10k-row directory. */
81
96
  function useRowPitchAudit(scrollEl, nodes, items) {
82
- useEffect(() => {}, [
97
+ useEffect(() => {
98
+ if (!isDev() || !scrollEl || items.length < 3) return;
99
+ const rows = [...scrollEl.querySelectorAll("tbody tr")].filter((row) => !row.hasAttribute("aria-hidden")).map((row, i) => {
100
+ const node = nodes[items[i]?.index ?? -1];
101
+ return node ? {
102
+ kind: isTwoLineRow(node) ? "tall" : "short",
103
+ top: row.getBoundingClientRect().top
104
+ } : null;
105
+ }).filter((row) => row !== null);
106
+ const expected = {
107
+ short: 45,
108
+ tall: ROW_HEIGHT_TALL
109
+ };
110
+ for (const [kind, measured] of measurePitches(rows)) if (measured > 0 && Math.abs(measured - expected[kind]) > .5) {
111
+ const name = `ROW_HEIGHT_${kind.toUpperCase()}`;
112
+ console.warn(`FileRowsWindow: ${name} is ${expected[kind]} but the rendered pitch is ${measured}. Set ${name} = ${measured} in FileRow.tsx, or the scrollbar will not reach the last row.`);
113
+ }
114
+ }, [
83
115
  scrollEl,
84
116
  nodes,
85
117
  items
@@ -13,11 +13,13 @@ interface FileTableProps {
13
13
  onOpen: (node: TreeNode) => void;
14
14
  onToggleCheck: (node: TreeNode, checked: boolean) => void;
15
15
  onPreview: (node: TreeNode) => void;
16
+ /** The URL of a path; set, each row is a link to its node. */
17
+ pathHref?: ((path: string) => string) | undefined;
16
18
  /** Set when the folder has pages left to fetch. */
17
19
  loadMore?: LoadMoreState | undefined;
18
20
  }
19
21
  /** The file table. Give it a `key` of the folder path: a folder change must reset
20
22
  * the cursor, the scroll offset and the virtualizer's measurements, and a remount
21
23
  * does all three without a synchronising effect. */
22
- export declare function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll, getItems, openMenu, onOpen, onToggleCheck, onPreview, loadMore, }: FileTableProps): import("react").JSX.Element;
24
+ export declare function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll, getItems, openMenu, onOpen, onToggleCheck, onPreview, pathHref, loadMore, }: FileTableProps): import("react").JSX.Element;
23
25
  export {};
@@ -1,4 +1,5 @@
1
1
  import { cx } from "../cx.js";
2
+ import { useStrings } from "../components/Provider.js";
2
3
  import { Table } from "../components/Table.js";
3
4
  import { listTableFixedProps } from "../list/ListTable.js";
4
5
  import { fileRowHeight } from "./FileRow.js";
@@ -11,11 +12,12 @@ import { useVirtualizer } from "@tanstack/react-virtual";
11
12
  /** The file table. Give it a `key` of the folder path: a folder change must reset
12
13
  * the cursor, the scroll offset and the virtualizer's measurements, and a remount
13
14
  * does all three without a synchronising effect. */
14
- function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll, getItems, openMenu, onOpen, onToggleCheck, onPreview, loadMore }) {
15
+ function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll, getItems, openMenu, onOpen, onToggleCheck, onPreview, pathHref, loadMore }) {
15
16
  const [scrollEl, setScrollEl] = useState(null);
16
17
  const [activeIndex, setActiveIndex] = useState(-1);
17
18
  const [theadHeight, setTheadHeight] = useState(0);
18
19
  const rowIdPrefix = useId();
20
+ const t = useStrings().fileExplorer;
19
21
  useLayoutEffect(() => {
20
22
  const thead = scrollEl?.querySelector("thead");
21
23
  if (thead instanceof HTMLElement) setTheadHeight(thead.offsetHeight);
@@ -137,7 +139,7 @@ function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll
137
139
  wrapperClassName: "overflow-x-auto",
138
140
  tableProps: {
139
141
  role: "grid",
140
- "aria-label": "Files",
142
+ "aria-label": t.chrome.files,
141
143
  "aria-rowcount": loadMore ? -1 : nodes.length + 1,
142
144
  "aria-multiselectable": true,
143
145
  tabIndex: 0,
@@ -163,7 +165,8 @@ function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll
163
165
  openMenu,
164
166
  onOpen: handleOpen,
165
167
  onToggleCheck,
166
- onPreview
168
+ onPreview,
169
+ pathHref
167
170
  })]
168
171
  })
169
172
  });
@@ -13,6 +13,10 @@ interface TreeViewProps {
13
13
  onDropToFolder: (folderPath: string, files: FileList | DataTransferItemList) => void;
14
14
  onContextMenu: (e: React.MouseEvent, node: TreeNode) => void;
15
15
  onPreviewFile: (node: TreeNode) => void;
16
+ /** The URL of a path; set, each row is a link to its path. */
17
+ pathHref?: ((path: string) => string) | undefined;
18
+ /** A plain click on a row's link, which moves to its path on its own. */
19
+ onFollowLink?: ((node: TreeNode) => void) | undefined;
16
20
  /** Directories with pages left to fetch, and how to fetch them. */
17
21
  paging?: {
18
22
  incompletePaths: ReadonlySet<string>;
@@ -23,5 +27,5 @@ interface TreeViewProps {
23
27
  }
24
28
  /** The sidebar tree, flattened and windowed. The flat array is the single source
25
29
  * of truth for rendering, arrow keys and ARIA. */
26
- export declare function TreeView({ rootNodes, parentToChildrenMap, expandedPaths, selectedPath, loadingPaths, onToggleExpand, onSelect, onDropToFolder, onContextMenu, onPreviewFile, paging, }: TreeViewProps): import("react").JSX.Element;
30
+ export declare function TreeView({ rootNodes, parentToChildrenMap, expandedPaths, selectedPath, loadingPaths, onToggleExpand, onSelect, onDropToFolder, onContextMenu, onPreviewFile, pathHref, onFollowLink, paging, }: TreeViewProps): import("react").JSX.Element;
27
31
  export {};
@@ -1,12 +1,14 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { ChevronRightIcon, FolderIcon, FolderOpenIcon } from "../icons.js";
3
+ import { isDev } from "../dev.js";
3
4
  import { positionKeys } from "../components/keys.js";
4
5
  import { useStrings } from "../components/Provider.js";
6
+ import { AppLink, isPlainClick } from "../components/AppLink.js";
5
7
  import { Skeleton } from "../components/Skeleton.js";
6
- import { getParentPath } from "./lib/utils.js";
8
+ import { carriesFiles, getParentPath } from "./lib/utils.js";
7
9
  import { canPreview } from "./FilePreview/previewType.js";
8
10
  import { getNodeIcon } from "./helpers.js";
9
- import { findRowIndex, flattenTreeRows, nextFileLimit, stepRow } from "./treeRows.js";
11
+ import { edgeRow, findRowIndex, firstChildRow, flattenTreeRows, nextFileLimit, parentRow, stepRow } from "./treeRows.js";
10
12
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
13
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
12
14
  import { useVirtualizer } from "@tanstack/react-virtual";
@@ -22,8 +24,9 @@ function rowHeight(row) {
22
24
  }
23
25
  /** The sidebar tree, flattened and windowed. The flat array is the single source
24
26
  * of truth for rendering, arrow keys and ARIA. */
25
- function TreeView({ rootNodes, parentToChildrenMap, expandedPaths, selectedPath, loadingPaths, onToggleExpand, onSelect, onDropToFolder, onContextMenu, onPreviewFile, paging }) {
27
+ function TreeView({ rootNodes, parentToChildrenMap, expandedPaths, selectedPath, loadingPaths, onToggleExpand, onSelect, onDropToFolder, onContextMenu, onPreviewFile, pathHref, onFollowLink, paging }) {
26
28
  const [scrollEl, setScrollEl] = useState(null);
29
+ const treeLabel = useStrings().fileExplorer.chrome.folders;
27
30
  const [fileLimits, setFileLimits] = useState(() => /* @__PURE__ */ new Map());
28
31
  const incompletePaths = paging?.incompletePaths;
29
32
  const rows = useMemo(() => flattenTreeRows(rootNodes, parentToChildrenMap, expandedPaths, {
@@ -73,11 +76,11 @@ function TreeView({ rootNodes, parentToChildrenMap, expandedPaths, selectedPath,
73
76
  getItemKey: getRowKey,
74
77
  overscan: 10
75
78
  });
76
- const move = useCallback((from, direction) => {
79
+ const move = useCallback((from, pick) => {
77
80
  const cursorIndex = findRowIndex(rows, cursorRef.current ?? "");
78
81
  const index = cursorIndex >= 0 ? cursorIndex : findRowIndex(rows, from.path);
79
82
  if (index === -1) return;
80
- const target = stepRow(rows, index, direction);
83
+ const target = pick(index);
81
84
  if (!target) return;
82
85
  cursorRef.current = target.node.path;
83
86
  pendingNavRef.current.add(target.node.path);
@@ -110,14 +113,23 @@ function TreeView({ rootNodes, parentToChildrenMap, expandedPaths, selectedPath,
110
113
  onPreviewFile(node);
111
114
  return;
112
115
  }
113
- if (e.key === "ArrowDown") {
116
+ const target = {
117
+ ArrowDown: (i) => stepRow(rows, i, 1),
118
+ ArrowUp: (i) => stepRow(rows, i, -1),
119
+ ArrowRight: (i) => firstChildRow(rows, i),
120
+ ArrowLeft: (i) => parentRow(rows, i),
121
+ Home: () => edgeRow(rows, "first"),
122
+ End: () => edgeRow(rows, "last")
123
+ }[e.key];
124
+ if (target) {
114
125
  e.preventDefault();
115
- move(node, 1);
116
- } else if (e.key === "ArrowUp") {
117
- e.preventDefault();
118
- move(node, -1);
126
+ move(node, target);
119
127
  }
120
- }, [move, onPreviewFile]);
128
+ }, [
129
+ move,
130
+ onPreviewFile,
131
+ rows
132
+ ]);
121
133
  const items = virtualizer.getVirtualItems();
122
134
  useTreePitchAudit(scrollEl, rows, items);
123
135
  const renderRow = (index, top, height) => {
@@ -140,6 +152,8 @@ function TreeView({ rootNodes, parentToChildrenMap, expandedPaths, selectedPath,
140
152
  }) : /* @__PURE__ */ jsx(TreeRowItem, {
141
153
  row,
142
154
  isSelected: row.node.path === selectedPath,
155
+ href: pathHref?.(row.node.path),
156
+ onFollowLink,
143
157
  onToggleExpand,
144
158
  onSelect,
145
159
  onDropToFolder,
@@ -167,6 +181,7 @@ function TreeView({ rootNodes, parentToChildrenMap, expandedPaths, selectedPath,
167
181
  ref: setScrollEl,
168
182
  className: "overflow-auto flex-1 px-2 py-2",
169
183
  role: "tree",
184
+ "aria-label": treeLabel,
170
185
  onDragOver: (e) => e.preventDefault(),
171
186
  onDrop: (e) => e.preventDefault(),
172
187
  children: /* @__PURE__ */ jsx("div", {
@@ -212,7 +227,7 @@ function TreeMoreRow({ depth, hiddenFiles, partial, loading, failed, onReveal })
212
227
  style: { paddingLeft: depth * INDENT_PER_LEVEL + ROW_PAD_FILE },
213
228
  children: loading ? /* @__PURE__ */ jsx("span", { children: t.loadingMore }) : /* @__PURE__ */ jsx("button", {
214
229
  type: "button",
215
- className: cx("cursor-pointer underline", failed ? "text-red-500" : "theme-link"),
230
+ className: cx("cursor-pointer underline", failed ? "text-(--theme-error-text)" : "theme-link"),
216
231
  onClick: onReveal,
217
232
  children: label()
218
233
  })
@@ -257,6 +272,7 @@ function useSpringLoadedDrop({ node, dropPath, isDirectory, isExpanded, onToggle
257
272
  dragHandlers: {
258
273
  onDragEnter: (e) => {
259
274
  e.preventDefault();
275
+ if (!carriesFiles(e.dataTransfer)) return;
260
276
  e.stopPropagation();
261
277
  hoverCount.current += 1;
262
278
  setDragActive(true);
@@ -268,6 +284,7 @@ function useSpringLoadedDrop({ node, dropPath, isDirectory, isExpanded, onToggle
268
284
  onDragOver: (e) => e.preventDefault(),
269
285
  onDragLeave: (e) => {
270
286
  e.preventDefault();
287
+ if (!carriesFiles(e.dataTransfer)) return;
271
288
  e.stopPropagation();
272
289
  hoverCount.current -= 1;
273
290
  if (hoverCount.current > 0) return;
@@ -275,8 +292,9 @@ function useSpringLoadedDrop({ node, dropPath, isDirectory, isExpanded, onToggle
275
292
  clear();
276
293
  },
277
294
  onDrop: (e) => {
278
- e.stopPropagation();
279
295
  e.preventDefault();
296
+ if (!carriesFiles(e.dataTransfer)) return;
297
+ e.stopPropagation();
280
298
  hoverCount.current = 0;
281
299
  setDragActive(false);
282
300
  clear();
@@ -285,11 +303,12 @@ function useSpringLoadedDrop({ node, dropPath, isDirectory, isExpanded, onToggle
285
303
  }
286
304
  };
287
305
  }
288
- /** Keys a row handles itself; the arrows that move between rows delegate upward,
289
- * where the flat array is. The set is deliberately narrow:
290
- * no Home/End, no typeahead, and ArrowLeft/Right only toggle a directory. */
291
- function dispatchRowKey(e, { node, isDirectory, isExpanded, onSelect, onToggleExpand, onDelegate }) {
306
+ /** Keys a row handles itself: Enter, Space, and Right or Left on a folder that
307
+ * can open or close. The keys that move between rows delegate upward, where
308
+ * the flat array is. There is no typeahead. */
309
+ function dispatchRowKey(e, { node, isDirectory, isExpanded, isLink, onSelect, onToggleExpand, onDelegate }) {
292
310
  if (e.key === "Enter") {
311
+ if (isLink && (e.metaKey || e.ctrlKey || e.shiftKey)) return;
293
312
  e.preventDefault();
294
313
  onSelect(e);
295
314
  return;
@@ -307,7 +326,7 @@ function dispatchRowKey(e, { node, isDirectory, isExpanded, onSelect, onToggleEx
307
326
  }
308
327
  onDelegate(e, node);
309
328
  }
310
- var TreeRowItem = memo(function TreeRowItem({ row, isSelected, onToggleExpand, onSelect, onDropToFolder, onContextMenu, onKeyDown }) {
329
+ var TreeRowItem = memo(function TreeRowItem({ row, isSelected, href, onFollowLink, onToggleExpand, onSelect, onDropToFolder, onContextMenu, onKeyDown }) {
311
330
  const { node, depth, isExpanded } = row;
312
331
  const isDirectory = node.type === "directory";
313
332
  const { dragActive, dragHandlers } = useSpringLoadedDrop({
@@ -323,47 +342,75 @@ var TreeRowItem = memo(function TreeRowItem({ row, isSelected, onToggleExpand, o
323
342
  onSelect(node.path, node);
324
343
  if (isDirectory) onToggleExpand(node.path);
325
344
  };
345
+ const handleLinkClick = (e) => {
346
+ if (!isPlainClick(e)) return;
347
+ onFollowLink?.(node);
348
+ if (isDirectory) onToggleExpand(node.path);
349
+ };
326
350
  const handleKeyDown = (e) => dispatchRowKey(e, {
327
351
  node,
328
352
  isDirectory,
329
353
  isExpanded,
354
+ isLink: !!href,
330
355
  onSelect: handleSelect,
331
356
  onToggleExpand,
332
357
  onDelegate: onKeyDown
333
358
  });
334
359
  const customIcon = getNodeIcon(node);
335
360
  const label = isDirectory && node.storageId && node.displayName ? node.displayName : node.name;
361
+ const tabIndex = isSelected ? 0 : -1;
362
+ const itemProps = {
363
+ className: cx("group relative flex items-center gap-1.5 pr-2 py-1 rounded-md cursor-pointer", "transition-colors duration-150", "focus:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-(--theme-link)", isSelected ? "bg-(--theme-accent)/15 font-medium" : dragActive ? "theme-hover" : "hover:theme-hover"),
364
+ style: indentStyle(depth, isDirectory),
365
+ onContextMenu: (e) => onContextMenu(e, node),
366
+ "aria-level": depth + 1,
367
+ "aria-posinset": row.posInSet,
368
+ "aria-setsize": row.setSize,
369
+ "aria-selected": isSelected,
370
+ ...isDirectory && { "aria-expanded": isExpanded },
371
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(RowIcons, {
372
+ isDirectory,
373
+ isExpanded,
374
+ customIcon
375
+ }), /* @__PURE__ */ jsx("span", {
376
+ className: "text-sm truncate",
377
+ children: label
378
+ })] })
379
+ };
336
380
  return /* @__PURE__ */ jsxs("div", {
337
381
  className: cx("relative h-full rounded-md transition-[box-shadow,background-color] duration-150", dragActive && "ring-1 ring-(--theme-link)/60"),
338
382
  ...dragHandlers,
339
- children: [/* @__PURE__ */ jsx(IndentGuides, { depth }), /* @__PURE__ */ jsxs("div", {
340
- className: cx("group relative flex items-center gap-1.5 pr-2 py-1 rounded-md cursor-pointer", "transition-colors duration-150", "focus:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-(--theme-link)", isSelected ? "bg-(--theme-accent)/15 font-medium" : dragActive ? "theme-hover" : "hover:theme-hover"),
341
- style: indentStyle(depth, isDirectory),
342
- onClick: handleSelect,
343
- onKeyDown: handleKeyDown,
344
- onContextMenu: (e) => onContextMenu(e, node),
383
+ children: [/* @__PURE__ */ jsx(IndentGuides, { depth }), href ? /* @__PURE__ */ jsx(AppLink, {
384
+ ...itemProps,
385
+ role: "treeitem",
386
+ tabIndex,
387
+ href,
388
+ onClick: handleLinkClick,
389
+ onKeyDown: handleKeyDown
390
+ }) : /* @__PURE__ */ jsx("div", {
391
+ ...itemProps,
345
392
  role: "treeitem",
346
- "aria-level": depth + 1,
347
- "aria-posinset": row.posInSet,
348
- "aria-setsize": row.setSize,
349
- "aria-selected": isSelected,
350
- ...isDirectory && { "aria-expanded": isExpanded },
351
- tabIndex: isSelected ? 0 : -1,
352
- children: [/* @__PURE__ */ jsx(RowIcons, {
353
- isDirectory,
354
- isExpanded,
355
- customIcon
356
- }), /* @__PURE__ */ jsx("span", {
357
- className: "text-sm truncate",
358
- children: label
359
- })]
393
+ tabIndex,
394
+ onClick: handleSelect,
395
+ onKeyDown: handleKeyDown
360
396
  })]
361
397
  });
362
398
  });
363
399
  /** The height constants must equal the rendered pitch, or the scrollbar
364
400
  * drifts from the content. Reports the measured value to use. */
365
401
  function useTreePitchAudit(scrollEl, rows, items) {
366
- useEffect(() => {}, [
402
+ useEffect(() => {
403
+ if (!isDev() || !scrollEl || items.length < 2) return;
404
+ for (const item of items) {
405
+ const row = rows[item.index];
406
+ const el = scrollEl.querySelector(`[data-index="${item.index}"]`);
407
+ if (row?.kind !== "node" || !(el instanceof HTMLElement)) continue;
408
+ const inner = el.firstElementChild?.lastElementChild;
409
+ const measured = inner ? inner.getBoundingClientRect().height : 0;
410
+ if (measured > 30.5) console.warn(`TreeView: TREE_ROW_HEIGHT is 30 but a row's content measures ${measured}. Set TREE_ROW_HEIGHT = ${Math.ceil(measured)} in TreeView.tsx.`);
411
+ return;
412
+ }
413
+ }, [
367
414
  scrollEl,
368
415
  rows,
369
416
  items
@@ -19,6 +19,7 @@ declare function CorsIssuePreview({ selectedStorage, onRefresh, }: {
19
19
  selectedStorage: TStorage | null;
20
20
  onRefresh?: CorsAwareRefresh | undefined;
21
21
  }): JSX.Element;
22
+ declare function CorsPropagatingPreview(): JSX.Element;
22
23
  declare function NoDataAvailablePreview({ selectedStorage, error, onRefresh, connectionIssueMessage, className, canWrite, }: {
23
24
  selectedStorage: TStorage | null;
24
25
  error?: Error | null;
@@ -39,4 +40,4 @@ interface IShareFileButtonProps {
39
40
  onShare: (node: TreeNode) => Promise<void>;
40
41
  }
41
42
  declare function ShareFileButton({ node, onShare }: IShareFileButtonProps): JSX.Element;
42
- export { CorsIssuePreview, DragDropZone, getNodeIcon, NoDataAvailablePreview, NoFileSelectedPreview, NotFoundPreview, renderUploadNodes, ShareFileButton, SIDEBAR_WIDTH_DEFAULT, SIDEBAR_WIDTH_STORAGE_KEY, SkeletonPreview, };
43
+ export { CorsIssuePreview, CorsPropagatingPreview, DragDropZone, getNodeIcon, NoDataAvailablePreview, NoFileSelectedPreview, NotFoundPreview, renderUploadNodes, ShareFileButton, SIDEBAR_WIDTH_DEFAULT, SIDEBAR_WIDTH_STORAGE_KEY, SkeletonPreview, };