@parallelworks/ui 0.50.0 → 0.52.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 (240) hide show
  1. package/README.md +204 -18
  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 +12 -0
  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 +4 -4
  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 +117 -56
  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 +81 -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 +21 -9
  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 +51 -24
  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 +51 -17
  63. package/dist/components/DescriptionList.js +117 -31
  64. package/dist/components/Disclosure.js +1 -1
  65. package/dist/components/Drawer.js +10 -16
  66. package/dist/components/Field.d.ts +15 -5
  67. package/dist/components/Field.js +41 -9
  68. package/dist/components/IconButton.d.ts +14 -5
  69. package/dist/components/IconButton.js +25 -14
  70. package/dist/components/InfoTip.d.ts +4 -1
  71. package/dist/components/InfoTip.js +2 -1
  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/MenuEntries.d.ts +43 -12
  76. package/dist/components/MenuEntries.js +34 -19
  77. package/dist/components/Meter.js +5 -4
  78. package/dist/components/Modal.d.ts +28 -4
  79. package/dist/components/Modal.js +37 -14
  80. package/dist/components/Overview.d.ts +20 -12
  81. package/dist/components/Overview.js +14 -9
  82. package/dist/components/PageHeader.d.ts +47 -65
  83. package/dist/components/PageHeader.js +275 -153
  84. package/dist/components/PageHeaderAction.d.ts +67 -0
  85. package/dist/components/PageHeaderAction.js +107 -0
  86. package/dist/components/Pagination.js +6 -16
  87. package/dist/components/Popover.js +3 -2
  88. package/dist/components/Provider.d.ts +79 -19
  89. package/dist/components/Provider.js +140 -130
  90. package/dist/components/Radio.js +2 -1
  91. package/dist/components/RefreshButton.js +7 -8
  92. package/dist/components/RequiredMark.js +1 -1
  93. package/dist/components/RunStatusIcon.js +2 -2
  94. package/dist/components/Section.js +1 -1
  95. package/dist/components/Select.d.ts +4 -9
  96. package/dist/components/Select.js +24 -11
  97. package/dist/components/SettingModeOption.d.ts +1 -1
  98. package/dist/components/SettingModeOption.js +7 -1
  99. package/dist/components/SettingsCard.d.ts +5 -3
  100. package/dist/components/SettingsCard.js +27 -20
  101. package/dist/components/Slider.js +1 -1
  102. package/dist/components/StatusBadge.js +2 -2
  103. package/dist/components/StatusIcons.js +2 -2
  104. package/dist/components/Switch.js +5 -4
  105. package/dist/components/Table.d.ts +30 -20
  106. package/dist/components/Table.js +50 -73
  107. package/dist/components/Tabs.d.ts +3 -7
  108. package/dist/components/Tabs.js +6 -10
  109. package/dist/components/ThemeEditor.js +24 -14
  110. package/dist/components/Toast.d.ts +11 -3
  111. package/dist/components/Toast.js +47 -22
  112. package/dist/components/buttonClasses.js +2 -2
  113. package/dist/components/comboboxItems.d.ts +4 -0
  114. package/dist/components/comboboxItems.js +6 -1
  115. package/dist/components/dismiss.d.ts +12 -0
  116. package/dist/components/dismiss.js +19 -0
  117. package/dist/components/fieldBox.d.ts +4 -0
  118. package/dist/components/fieldBox.js +7 -3
  119. package/dist/components/floating.d.ts +3 -3
  120. package/dist/components/floating.js +1 -1
  121. package/dist/components/markdownCodeBreaks.js +3 -4
  122. package/dist/components/pageHeaderStore.d.ts +41 -0
  123. package/dist/components/pageHeaderStore.js +67 -0
  124. package/dist/components/pathBreaks.d.ts +8 -0
  125. package/dist/components/pathBreaks.js +28 -0
  126. package/dist/components/segmented.js +1 -1
  127. package/dist/components/shortcutKey.d.ts +2 -1
  128. package/dist/components/shortcutKey.js +19 -2
  129. package/dist/components/splitter.js +1 -1
  130. package/dist/components/tone.d.ts +5 -2
  131. package/dist/components/tone.js +15 -6
  132. package/dist/components/useCssIsDark.js +40 -18
  133. package/dist/components/useTheme.js +9 -10
  134. package/dist/dev.d.ts +9 -0
  135. package/dist/dev.js +14 -0
  136. package/dist/editor/Editor.d.ts +3 -2
  137. package/dist/editor/Editor.js +11 -2
  138. package/dist/editor/EditorField.d.ts +2 -1
  139. package/dist/editor/EditorField.js +11 -29
  140. package/dist/editor/FormCodePanel.d.ts +7 -3
  141. package/dist/editor/FormCodePanel.js +14 -7
  142. package/dist/editor/Monaco.d.ts +14 -5
  143. package/dist/editor/Monaco.js +163 -112
  144. package/dist/editor/index.d.ts +5 -5
  145. package/dist/editor/index.js +3 -3
  146. package/dist/editor/themes.d.ts +8 -5
  147. package/dist/editor/themes.js +58 -30
  148. package/dist/editor/yaml.d.ts +6 -2
  149. package/dist/editor/yaml.js +11 -2
  150. package/dist/file-explorer/FileExplorer.js +143 -124
  151. package/dist/file-explorer/FileExplorerContext.js +1 -1
  152. package/dist/file-explorer/FilePreview/StreamedBadge.js +3 -1
  153. package/dist/file-explorer/FilePreview/Viewer.js +2 -2
  154. package/dist/file-explorer/FilePreview/index.js +4 -4
  155. package/dist/file-explorer/FileRow.js +1 -1
  156. package/dist/file-explorer/FileRowsWindow.js +34 -3
  157. package/dist/file-explorer/FileTable.js +3 -1
  158. package/dist/file-explorer/TreeView.js +37 -14
  159. package/dist/file-explorer/helpers.d.ts +2 -1
  160. package/dist/file-explorer/helpers.js +21 -20
  161. package/dist/file-explorer/toasts.js +27 -19
  162. package/dist/file-explorer/treeRows.d.ts +6 -0
  163. package/dist/file-explorer/treeRows.js +19 -1
  164. package/dist/file-explorer/useNearEnd.js +3 -6
  165. package/dist/form/Fields/CheckboxGroupField.js +12 -5
  166. package/dist/form/Fields/DurationField.js +1 -1
  167. package/dist/form/Fields/LabelField.js +1 -1
  168. package/dist/form/Fields/ObjectField.d.ts +1 -1
  169. package/dist/form/Fields/ObjectField.js +3 -2
  170. package/dist/form/Fields/StringField.js +16 -29
  171. package/dist/form/Fields/TextAreaField.js +3 -4
  172. package/dist/form/Form.js +3 -8
  173. package/dist/form/Wizard/WizardNavigation.js +1 -4
  174. package/dist/form/Wizard/WizardStepIndicator.js +1 -1
  175. package/dist/form/Wizard/utils.js +1 -1
  176. package/dist/form/fieldFrame.d.ts +1 -1
  177. package/dist/form/fieldFrame.js +2 -2
  178. package/dist/form/types/fieldComponentTypes.d.ts +2 -1
  179. package/dist/graph/AnnotationBanner.d.ts +1 -2
  180. package/dist/graph/AnnotationBanner.js +30 -32
  181. package/dist/graph/Connectors.js +4 -1
  182. package/dist/graph/DependencyGraph.js +2 -2
  183. package/dist/graph/GraphEditor.js +12 -10
  184. package/dist/graph/GraphEditorDialogs.js +2 -2
  185. package/dist/graph/GraphNode.js +7 -4
  186. package/dist/graph/InputDialog.js +1 -1
  187. package/dist/graph/InputsEditor.js +14 -11
  188. package/dist/graph/MatrixGroup.js +9 -3
  189. package/dist/graph/ProblemsButton.js +2 -2
  190. package/dist/graph/Reveal.js +18 -10
  191. package/dist/graph/ShortcutsButton.d.ts +11 -2
  192. package/dist/graph/ShortcutsButton.js +15 -4
  193. package/dist/graph/TreeView.js +35 -18
  194. package/dist/graph/WorkflowSettingsDialog.js +1 -1
  195. package/dist/graph/editorChrome.js +1 -1
  196. package/dist/graph/editorFields.js +1 -1
  197. package/dist/graph/editorRows.js +16 -10
  198. package/dist/graph/editorStrings.d.ts +7 -0
  199. package/dist/graph/editorStrings.js +8 -1
  200. package/dist/graph/fieldProblems.js +1 -1
  201. package/dist/graph/inputRefs.js +1 -1
  202. package/dist/graph/settingsViews.js +11 -18
  203. package/dist/graph/stepWith.js +2 -2
  204. package/dist/icons.d.ts +2 -0
  205. package/dist/icons.js +4 -2
  206. package/dist/index.d.ts +12 -12
  207. package/dist/index.js +16 -16
  208. package/dist/list/ActionToolbar.d.ts +44 -8
  209. package/dist/list/ActionToolbar.js +301 -102
  210. package/dist/list/ListPage.d.ts +15 -23
  211. package/dist/list/ListPage.js +46 -45
  212. package/dist/list/ListTable.d.ts +32 -7
  213. package/dist/list/ListTable.js +48 -12
  214. package/dist/list/ListViewControls.js +2 -2
  215. package/dist/list/UserHoverCard.js +7 -15
  216. package/dist/list/index.d.ts +2 -2
  217. package/dist/list/index.js +2 -2
  218. package/dist/list/listSearch.js +12 -10
  219. package/dist/list/listView.d.ts +10 -1
  220. package/dist/list/listView.js +15 -1
  221. package/dist/list/rowMenu.d.ts +2 -2
  222. package/dist/logviewer/LogViewer.d.ts +4 -2
  223. package/dist/logviewer/LogViewer.js +88 -70
  224. package/dist/logviewer/logviewer.css +3 -2
  225. package/dist/reducedMotion.d.ts +5 -0
  226. package/dist/reducedMotion.js +20 -0
  227. package/dist/safeUrl.d.ts +1 -1
  228. package/dist/safeUrl.js +4 -2
  229. package/dist/styles/theme.css +24 -10
  230. package/dist/styles.css +1 -1
  231. package/dist/theme/derive.d.ts +1 -1
  232. package/dist/theme/derive.js +46 -21
  233. package/dist/theme/fit.js +10 -4
  234. package/package.json +1 -1
  235. package/dist/components/Breadcrumbs.d.ts +0 -37
  236. package/dist/components/Breadcrumbs.js +0 -90
  237. package/dist/components/CopyToClipboard.d.ts +0 -17
  238. package/dist/components/CopyToClipboard.js +0 -66
  239. package/dist/components/Sort.d.ts +0 -9
  240. package/dist/components/Sort.js +0 -42
@@ -1,26 +1,28 @@
1
1
  import { cx } from "../cx.js";
2
- import { AccessIcon, ChevronDownIcon, ChevronRightIcon, CollapseIcon, CopyIcon, DownloadIcon, FileIcon, FileTextIcon, FolderIcon, LinkIcon, PreviewIcon, ShareIcon, TrashIcon, UploadIcon, WarningIcon } from "../icons.js";
2
+ import { buttonClasses } from "../components/buttonClasses.js";
3
+ import { AccessIcon, ChevronDownIcon, ChevronRightIcon, CollapseIcon, DownloadIcon, FileIcon, FileTextIcon, FolderIcon, LinkIcon, PreviewIcon, ShareIcon, TrashIcon, UploadIcon } from "../icons.js";
3
4
  import { MenuItem, MenuPopup, MenuSeparator } from "../components/Menu.js";
4
5
  import { safeUrl } from "../safeUrl.js";
5
- import { keyedByContent } from "../components/keys.js";
6
- import { buttonClasses } from "../components/buttonClasses.js";
7
6
  import { Loader } from "../components/Loader.js";
8
7
  import { toast } from "../components/Toast.js";
8
+ import { keyedByContent } from "../components/keys.js";
9
9
  import { Tooltip } from "../components/Tooltip.js";
10
10
  import { usePersistedState, useSlots, useStrings } from "../components/Provider.js";
11
11
  import { useCopy } from "../components/useCopy.js";
12
12
  import { Button } from "../components/Button.js";
13
+ import { Callout } from "../components/Callout.js";
14
+ import { InfoTip } from "../components/InfoTip.js";
13
15
  import { ConfirmModal } from "../components/ConfirmModal.js";
14
16
  import { useContextMenu } from "../components/ContextMenu.js";
15
- import CopyToClipboard, { CopyCodeBlock } from "../components/CopyToClipboard.js";
17
+ import { CopyButton, CopyCodeBlock } from "../components/CopyButton.js";
16
18
  import { CreateModal } from "../components/CreateModal.js";
17
- import { InfoTip } from "../components/InfoTip.js";
18
19
  import { useSplitter } from "../components/splitter.js";
19
20
  import { formatBytes, formatDateTime } from "../format.js";
20
21
  import { RelativeTime } from "../components/relativeTime.js";
21
22
  import { RefreshButton } from "../components/RefreshButton.js";
22
23
  import { StatusBadge } from "../components/StatusBadge.js";
23
24
  import { Table } from "../components/Table.js";
25
+ import { Tabs } from "../components/Tabs.js";
24
26
  import { ListActionsHeader, ListSkeleton, listTableFixedProps, listTableProps } from "../list/ListTable.js";
25
27
  import { assembleRowMenu, useCopySubmenu } from "../list/rowMenu.js";
26
28
  import { calculateUploadNodesTotalSize, createTreeBuilder, getDirectoryNameError, getDirectoryObjectPath, getNodeChildren, getObjectKeyFromNode, getParentPath, getUploadNodesFromDataTransferItems, getUriScheme, pruneNodesInsideSelectedDirectories, removeLeadingAndTrailingSlashes } from "./lib/utils.js";
@@ -42,12 +44,10 @@ import { useCallback, useEffect, useEffectEvent, useLayoutEffect, useMemo, useRe
42
44
  import { DateTime } from "luxon";
43
45
  //#region src/file-explorer/FileExplorer.tsx
44
46
  function UriCard({ value }) {
45
- const { common } = useStrings();
47
+ const { fileExplorer: t } = useStrings();
46
48
  const scheme = getUriScheme(value);
47
- return /* @__PURE__ */ jsxs(CopyToClipboard, {
48
- text: value,
49
- showIcon: false,
50
- className: "group flex items-stretch rounded-lg border theme-border bg-(--theme-input-bg) overflow-hidden cursor-copy hover:border-(--theme-link)/50 transition-colors duration-150",
49
+ return /* @__PURE__ */ jsxs("div", {
50
+ className: "flex items-stretch overflow-hidden rounded-lg border theme-border bg-(--theme-input-bg)",
51
51
  children: [
52
52
  scheme && /* @__PURE__ */ jsx("span", {
53
53
  className: "shrink-0 inline-flex items-center px-3 bg-(--theme-muted-panel-bg) border-r theme-border font-mono text-[11px] font-semibold uppercase tracking-widest theme-muted-text",
@@ -58,9 +58,11 @@ function UriCard({ value }) {
58
58
  children: value
59
59
  }),
60
60
  /* @__PURE__ */ jsx("span", {
61
- className: "shrink-0 inline-flex items-center justify-center px-3 border-l theme-border theme-muted-text group-hover:text-(--theme-link) group-hover:bg-(--theme-link)/5 transition-colors duration-150",
62
- title: common.copyToClipboard,
63
- children: /* @__PURE__ */ jsx(CopyIcon, { className: "h-3.5 w-3.5" })
61
+ className: "shrink-0 inline-flex items-center border-l theme-border px-1.5",
62
+ children: /* @__PURE__ */ jsx(CopyButton, {
63
+ text: value,
64
+ "aria-label": t.preview.copyUri
65
+ })
64
66
  })
65
67
  ]
66
68
  });
@@ -320,10 +322,10 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
320
322
  return { corsError: false };
321
323
  } catch (error) {
322
324
  const isCorsError = error instanceof CorsError;
323
- const errorMessage = isCorsError ? error.message || "CORS error" : error instanceof Error ? error.message : "Unknown error";
325
+ const errorMessage = error instanceof Error && error.message || t.messages.unknownError;
324
326
  if (owns()) {
325
327
  setListings((prev) => recordListingError(options?.replace ? clearListingsUnder(prev, node.path) : prev, node.path, errorMessage));
326
- updateConnectionIssuesMessage((prev) => new Map(prev).set(nodeStorageId, `Failed to load contents for ${node.displayName || node.name}: ${errorMessage}`));
328
+ updateConnectionIssuesMessage((prev) => new Map(prev).set(nodeStorageId, t.messages.loadFailed(node.displayName || node.name, errorMessage)));
327
329
  if (isCorsError) setHasCorsIssue((prev) => new Map(prev).set(nodeStorageId, true));
328
330
  }
329
331
  return { corsError: isCorsError };
@@ -687,22 +689,30 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
687
689
  }
688
690
  if (chain.length === 0) return null;
689
691
  return /* @__PURE__ */ jsx("nav", {
690
- className: "flex min-w-0 flex-1 items-center gap-1 overflow-hidden text-[13px]",
691
- children: chain.map((node, i) => {
692
- const isLast = i === chain.length - 1;
693
- const label = node.displayName || node.name;
694
- return /* @__PURE__ */ jsxs("span", {
695
- className: "flex min-w-0 items-center gap-1",
696
- children: [i > 0 && /* @__PURE__ */ jsx(ChevronRightIcon, { className: "h-3 w-3 shrink-0 text-(--theme-muted-text-color)" }), isLast ? /* @__PURE__ */ jsx("span", {
697
- className: "truncate font-semibold text-(--theme-app)",
698
- children: label
699
- }) : /* @__PURE__ */ jsx("button", {
700
- type: "button",
701
- onClick: () => handleSelectTreeItem(node.path, node),
702
- className: "max-w-[16rem] cursor-pointer truncate text-(--theme-muted-text-color) transition-colors hover:text-(--theme-app)",
703
- children: label
704
- })]
705
- }, node.path);
692
+ "aria-label": t.chrome.location,
693
+ className: "flex min-w-0 flex-1 overflow-hidden text-[13px]",
694
+ children: /* @__PURE__ */ jsx("ol", {
695
+ className: "flex min-w-0 items-center gap-1",
696
+ children: chain.map((node, i) => {
697
+ const isLast = i === chain.length - 1;
698
+ const label = node.displayName || node.name;
699
+ return /* @__PURE__ */ jsxs("li", {
700
+ className: "flex min-w-0 items-center gap-1",
701
+ children: [i > 0 && /* @__PURE__ */ jsx(ChevronRightIcon, {
702
+ "aria-hidden": "true",
703
+ className: "h-3 w-3 shrink-0 text-(--theme-muted-text-color) rtl:rotate-180"
704
+ }), isLast ? /* @__PURE__ */ jsx("span", {
705
+ "aria-current": "location",
706
+ className: "truncate font-semibold text-(--theme-app)",
707
+ children: label
708
+ }) : /* @__PURE__ */ jsx("button", {
709
+ type: "button",
710
+ onClick: () => handleSelectTreeItem(node.path, node),
711
+ className: "max-w-[16rem] cursor-pointer truncate text-(--theme-muted-text-color) transition-colors hover:text-(--theme-app)",
712
+ children: label
713
+ })]
714
+ }, node.path);
715
+ })
706
716
  })
707
717
  });
708
718
  };
@@ -1056,33 +1066,41 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1056
1066
  handleUploadInputChange(e.target.files, selectedNode);
1057
1067
  }
1058
1068
  }),
1059
- selectedNodeCanWrite && selectedNodeCanUpload && /* @__PURE__ */ jsx(Button, {
1060
- variant: "primary",
1061
- size: "sm",
1062
- "aria-label": t.createFolder.action,
1063
- icon: /* @__PURE__ */ jsx(FolderIcon, {}),
1064
- disabled: selectedNode.type === "file" || !selectedNode.storageId,
1065
- onClick: () => setCreateFolderTarget(selectedNode),
1066
- children: /* @__PURE__ */ jsx("span", {
1067
- className: "hidden @4xl:inline",
1068
- children: t.createFolder.action
1069
+ selectedNodeCanWrite && selectedNodeCanUpload && /* @__PURE__ */ jsx(Tooltip, {
1070
+ content: t.createFolder.action,
1071
+ side: "bottom",
1072
+ children: /* @__PURE__ */ jsx(Button, {
1073
+ variant: "outline",
1074
+ size: "sm",
1075
+ "aria-label": t.createFolder.action,
1076
+ icon: /* @__PURE__ */ jsx(FolderIcon, {}),
1077
+ disabled: selectedNode.type === "file" || !selectedNode.storageId,
1078
+ onClick: () => setCreateFolderTarget(selectedNode),
1079
+ children: /* @__PURE__ */ jsx("span", {
1080
+ className: "hidden @4xl:inline",
1081
+ children: t.createFolder.action
1082
+ })
1069
1083
  })
1070
1084
  }),
1071
- selectedNodeCanWrite && selectedNodeCanUpload && /* @__PURE__ */ jsxs(Menu.Root, { children: [/* @__PURE__ */ jsxs(Menu.Trigger, {
1072
- "aria-label": t.chrome.upload,
1073
- className: buttonClasses({
1074
- variant: "primary",
1075
- size: "sm"
1076
- }),
1077
- disabled: selectedNode.type === "file" || !selectedNode.storageId,
1078
- children: [
1079
- /* @__PURE__ */ jsx(UploadIcon, {}),
1080
- /* @__PURE__ */ jsx("span", {
1081
- className: "hidden @4xl:inline",
1082
- children: t.chrome.upload
1085
+ selectedNodeCanWrite && selectedNodeCanUpload && /* @__PURE__ */ jsxs(Menu.Root, { children: [/* @__PURE__ */ jsx(Tooltip, {
1086
+ content: t.chrome.upload,
1087
+ side: "bottom",
1088
+ children: /* @__PURE__ */ jsxs(Menu.Trigger, {
1089
+ "aria-label": t.chrome.upload,
1090
+ className: buttonClasses({
1091
+ variant: selectedNode.type === "directory" ? "primary" : "outline",
1092
+ size: "sm"
1083
1093
  }),
1084
- /* @__PURE__ */ jsx(ChevronDownIcon, {})
1085
- ]
1094
+ disabled: selectedNode.type === "file" || !selectedNode.storageId,
1095
+ children: [
1096
+ /* @__PURE__ */ jsx(UploadIcon, {}),
1097
+ /* @__PURE__ */ jsx("span", {
1098
+ className: "hidden @4xl:inline",
1099
+ children: t.chrome.upload
1100
+ }),
1101
+ /* @__PURE__ */ jsx(ChevronDownIcon, {})
1102
+ ]
1103
+ })
1086
1104
  }), /* @__PURE__ */ jsxs(MenuPopup, {
1087
1105
  align: "end",
1088
1106
  className: "w-36",
@@ -1094,21 +1112,25 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1094
1112
  children: t.chrome.selectFolder
1095
1113
  })]
1096
1114
  })] }),
1097
- /* @__PURE__ */ jsxs(Menu.Root, { children: [/* @__PURE__ */ jsxs(Menu.Trigger, {
1098
- "aria-label": common.download,
1099
- className: buttonClasses({
1100
- variant: "primary",
1101
- size: "sm"
1102
- }),
1103
- disabled: checkedItems.size === 0 && selectedNode.type !== "file" || hasFolderSelected() || !selectedNode.storageId,
1104
- children: [
1105
- /* @__PURE__ */ jsx(DownloadIcon, {}),
1106
- /* @__PURE__ */ jsx("span", {
1107
- className: "hidden @4xl:inline",
1108
- children: common.download
1115
+ /* @__PURE__ */ jsxs(Menu.Root, { children: [/* @__PURE__ */ jsx(Tooltip, {
1116
+ content: common.download,
1117
+ side: "bottom",
1118
+ children: /* @__PURE__ */ jsxs(Menu.Trigger, {
1119
+ "aria-label": common.download,
1120
+ className: buttonClasses({
1121
+ variant: selectedNode.type === "file" ? "primary" : "outline",
1122
+ size: "sm"
1109
1123
  }),
1110
- /* @__PURE__ */ jsx(ChevronDownIcon, {})
1111
- ]
1124
+ disabled: checkedItems.size === 0 && selectedNode.type !== "file" || hasFolderSelected() || !selectedNode.storageId,
1125
+ children: [
1126
+ /* @__PURE__ */ jsx(DownloadIcon, {}),
1127
+ /* @__PURE__ */ jsx("span", {
1128
+ className: "hidden @4xl:inline",
1129
+ children: common.download
1130
+ }),
1131
+ /* @__PURE__ */ jsx(ChevronDownIcon, {})
1132
+ ]
1133
+ })
1112
1134
  }), /* @__PURE__ */ jsxs(MenuPopup, {
1113
1135
  align: "end",
1114
1136
  className: "w-72",
@@ -1121,7 +1143,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1121
1143
  className: "text-sm text-center pb-1.5",
1122
1144
  children: downloadCommand.label
1123
1145
  }), /* @__PURE__ */ jsx(CopyCodeBlock, {
1124
- textToCopy: downloadCommandText,
1146
+ text: downloadCommandText,
1125
1147
  children: /* @__PURE__ */ jsx("div", {
1126
1148
  className: "overflow-x-auto text-xs",
1127
1149
  children: keyedByContent(downloadCommandText.split("\n"), (l) => l).map(({ key, item: line }) => /* @__PURE__ */ jsx("div", {
@@ -1132,28 +1154,37 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1132
1154
  })]
1133
1155
  })] })]
1134
1156
  })] }),
1135
- groups.length > 0 && selectedNodeCanManageAccess && /* @__PURE__ */ jsx(Button, {
1136
- variant: "primary",
1137
- size: "sm",
1138
- "aria-label": t.chrome.access,
1139
- icon: /* @__PURE__ */ jsx(AccessIcon, {}),
1140
- disabled: !selectedNode.storageId,
1141
- onClick: () => setAccessDrawerOpen(true),
1142
- children: /* @__PURE__ */ jsx("span", {
1143
- className: "hidden @4xl:inline",
1144
- children: t.chrome.manageAccess
1157
+ groups.length > 0 && selectedNodeCanManageAccess && /* @__PURE__ */ jsx(Tooltip, {
1158
+ content: t.chrome.manageAccess,
1159
+ side: "bottom",
1160
+ children: /* @__PURE__ */ jsx(Button, {
1161
+ variant: "outline",
1162
+ size: "sm",
1163
+ "aria-label": t.chrome.access,
1164
+ icon: /* @__PURE__ */ jsx(AccessIcon, {}),
1165
+ disabled: !selectedNode.storageId,
1166
+ onClick: () => setAccessDrawerOpen(true),
1167
+ children: /* @__PURE__ */ jsx("span", {
1168
+ className: "hidden @4xl:inline",
1169
+ children: t.chrome.manageAccess
1170
+ })
1145
1171
  })
1146
1172
  }),
1147
- selectedNodeCanDelete && /* @__PURE__ */ jsx(Button, {
1148
- variant: "danger",
1149
- size: "sm",
1150
- "aria-label": common.delete,
1151
- icon: /* @__PURE__ */ jsx(TrashIcon, {}),
1152
- disabled: checkedItems.size === 0 && selectedNode.type !== "file" || !selectedNode.storageId,
1153
- onClick: onDeleteClick,
1154
- children: /* @__PURE__ */ jsx("span", {
1155
- className: "hidden @4xl:inline",
1156
- children: common.delete
1173
+ selectedNodeCanDelete && /* @__PURE__ */ jsx(Tooltip, {
1174
+ content: common.delete,
1175
+ side: "bottom",
1176
+ children: /* @__PURE__ */ jsx(Button, {
1177
+ variant: "outline",
1178
+ size: "sm",
1179
+ "aria-label": common.delete,
1180
+ icon: /* @__PURE__ */ jsx(TrashIcon, {}),
1181
+ className: "*:text-(--theme-error-text)",
1182
+ disabled: checkedItems.size === 0 && selectedNode.type !== "file" || !selectedNode.storageId,
1183
+ onClick: onDeleteClick,
1184
+ children: /* @__PURE__ */ jsx("span", {
1185
+ className: "hidden @4xl:inline",
1186
+ children: common.delete
1187
+ })
1157
1188
  })
1158
1189
  })
1159
1190
  ]
@@ -1202,22 +1233,20 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1202
1233
  const createdDt = selectedNode.created ? DateTime.fromISO(selectedNode.created) : null;
1203
1234
  const previewable = canPreview(selectedNode);
1204
1235
  const effectiveTab = previewable ? detailTab : "details";
1205
- const tabClass = (active) => cx("mx-2.5 border-b-2 px-1 py-2.5 text-[13px] font-medium cursor-pointer transition-colors", active ? "theme-text border-(--theme-accent)" : "theme-muted-text border-transparent hover:theme-text");
1206
1236
  return /* @__PURE__ */ jsxs("div", {
1207
1237
  className: "flex h-full min-h-0 flex-col",
1208
- children: [/* @__PURE__ */ jsxs("div", {
1209
- className: "flex shrink-0 items-center border-b theme-border px-3",
1210
- children: [/* @__PURE__ */ jsx("button", {
1211
- type: "button",
1212
- className: tabClass(effectiveTab === "details"),
1213
- onClick: () => setDetailTab("details"),
1214
- children: t.preview.detailsTab
1215
- }), previewable && /* @__PURE__ */ jsx("button", {
1216
- type: "button",
1217
- className: tabClass(effectiveTab === "preview"),
1218
- onClick: () => setDetailTab("preview"),
1219
- children: t.preview.previewTab
1220
- })]
1238
+ children: [/* @__PURE__ */ jsx(Tabs, {
1239
+ label: selectedNode.name,
1240
+ items: [{
1241
+ value: "details",
1242
+ label: t.preview.detailsTab
1243
+ }, ...previewable ? [{
1244
+ value: "preview",
1245
+ label: t.preview.previewTab
1246
+ }] : []],
1247
+ value: effectiveTab,
1248
+ onValueChange: setDetailTab,
1249
+ className: "shrink-0 px-3"
1221
1250
  }), effectiveTab === "preview" && previewable ? /* @__PURE__ */ jsx(FilePreviewInline, {
1222
1251
  node: selectedNode,
1223
1252
  storage: selectedStorage ?? null,
@@ -1274,12 +1303,12 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1274
1303
  node: selectedNode,
1275
1304
  onShare: onShareFileButtonClick
1276
1305
  }), /* @__PURE__ */ jsx(InfoTip, {
1277
- text: "This will generate a pre-signed URL for this file. Anyone with this link can temporarily access and download the file, even if they don't have an account. The link will automatically expire after 12 hours.",
1306
+ text: t.chrome.shareHelp,
1278
1307
  side: "right"
1279
1308
  })]
1280
1309
  }),
1281
1310
  /* @__PURE__ */ jsxs("div", {
1282
- className: "px-6 py-5 mt-5 border-t theme-border",
1311
+ className: cx("px-6 py-5", (uris.length > 0 || selectedNodeCanShare) && "mt-5 border-t theme-border"),
1283
1312
  children: [/* @__PURE__ */ jsx("h3", {
1284
1313
  className: "text-[11px] font-semibold uppercase tracking-widest theme-muted-text mb-3",
1285
1314
  children: t.chrome.details
@@ -1388,8 +1417,8 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1388
1417
  children: /* @__PURE__ */ jsxs("div", {
1389
1418
  className: "text-sm flex flex-col gap-2 mt-2 text-left",
1390
1419
  children: [
1391
- /* @__PURE__ */ jsx("ul", {
1392
- className: "list-disc pl-5 max-h-40 overflow-auto theme-muted-panel py-1",
1420
+ /* @__PURE__ */ jsx("div", {
1421
+ className: "max-h-40 overflow-auto theme-muted-panel py-1 pl-3",
1393
1422
  children: pendingUpload?.uploadNodes && renderUploadNodes(pendingUpload.uploadNodes)
1394
1423
  }),
1395
1424
  /* @__PURE__ */ jsxs("div", { children: [
@@ -1409,26 +1438,16 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
1409
1438
  }),
1410
1439
  " ",
1411
1440
  /* @__PURE__ */ jsx("span", {
1412
- className: cx(isUploadSizeExceeded(pendingUpload?.totalSize ?? 0) && "text-amber-800 font-semibold"),
1441
+ className: cx(isUploadSizeExceeded(pendingUpload?.totalSize ?? 0) && "text-(--theme-warning-text) font-semibold"),
1413
1442
  children: formatBytes(pendingUpload?.totalSize ?? 0)
1414
1443
  })
1415
1444
  ]
1416
1445
  }),
1417
- isUploadSizeExceeded(pendingUpload?.totalSize ?? 0) && /* @__PURE__ */ jsx("div", {
1418
- className: "mt-3 p-3 bg-amber-50 border border-amber-200 rounded-lg",
1419
- children: /* @__PURE__ */ jsxs("div", {
1420
- className: "flex items-start gap-2",
1421
- children: [/* @__PURE__ */ jsx(WarningIcon, { className: "w-4 h-4 text-amber-600 shrink-0 mt-0.5" }), /* @__PURE__ */ jsxs("div", {
1422
- className: "text-amber-800",
1423
- children: [/* @__PURE__ */ jsx("div", {
1424
- className: "font-semibold mb-1",
1425
- children: t.confirmUpload.sizeLimitTitle
1426
- }), /* @__PURE__ */ jsxs("div", {
1427
- className: "text-sm",
1428
- children: [/* @__PURE__ */ jsx("p", { children: t.confirmUpload.sizeLimitBody(formatBytes(pendingUpload?.totalSize ?? 0), formatBytes(MAX_UPLOAD_SIZE)) }), /* @__PURE__ */ jsx("p", { children: t.confirmUpload.sizeLimitHint })]
1429
- })]
1430
- })]
1431
- })
1446
+ isUploadSizeExceeded(pendingUpload?.totalSize ?? 0) && /* @__PURE__ */ jsxs(Callout, {
1447
+ tone: "warning",
1448
+ title: t.confirmUpload.sizeLimitTitle,
1449
+ className: "mt-3",
1450
+ children: [/* @__PURE__ */ jsx("p", { children: t.confirmUpload.sizeLimitBody(formatBytes(pendingUpload?.totalSize ?? 0), formatBytes(MAX_UPLOAD_SIZE)) }), /* @__PURE__ */ jsx("p", { children: t.confirmUpload.sizeLimitHint })]
1432
1451
  })
1433
1452
  ]
1434
1453
  })
@@ -1,5 +1,5 @@
1
- import { keyedByContent } from "../components/keys.js";
2
1
  import { toast } from "../components/Toast.js";
2
+ import { keyedByContent } from "../components/keys.js";
3
3
  import { useStrings } from "../components/Provider.js";
4
4
  import { mapWithConcurrency } from "./lib/batch.js";
5
5
  import { calculateUploadNodesTotalSize, getUploadSessionETA, removeLeadingSlash } from "./lib/utils.js";
@@ -1,4 +1,6 @@
1
+ import { cx } from "../../cx.js";
1
2
  import { LockIcon } from "../../icons.js";
3
+ import { toneSurface } from "../../components/tone.js";
2
4
  import { useStrings } from "../../components/Provider.js";
3
5
  import { InfoTip } from "../../components/InfoTip.js";
4
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -7,7 +9,7 @@ function StreamedBadge() {
7
9
  const t = useStrings().fileExplorer;
8
10
  return /* @__PURE__ */ jsxs(InfoTip, {
9
11
  text: t.preview.streamedTooltip,
10
- className: "shrink-0 gap-1 rounded-full border border-green-500/40 bg-green-500/15 px-2 py-0.5 text-[11px] text-green-600",
12
+ className: cx("shrink-0 gap-1 rounded-full border px-2 py-0.5 text-[11px]", toneSurface.success),
11
13
  children: [/* @__PURE__ */ jsx(LockIcon, { className: "h-2.5 w-2.5" }), t.preview.streamed]
12
14
  });
13
15
  }
@@ -1,7 +1,7 @@
1
1
  import { cx } from "../../cx.js";
2
2
  import { DownloadIcon, FileIcon, LockIcon, MusicIcon, ZipIcon } from "../../icons.js";
3
- import { positionKeys, withPositionKeys } from "../../components/keys.js";
4
3
  import { Loader } from "../../components/Loader.js";
4
+ import { positionKeys, withPositionKeys } from "../../components/keys.js";
5
5
  import { useStrings } from "../../components/Provider.js";
6
6
  import { Button } from "../../components/Button.js";
7
7
  import Markdown from "../../components/Markdown.js";
@@ -439,7 +439,7 @@ function NotebookOutputView({ output }) {
439
439
  children: joinSource(output.text)
440
440
  });
441
441
  if (output.output_type === "error") return /* @__PURE__ */ jsx("pre", {
442
- className: "mt-2 overflow-auto rounded-lg bg-red-500/15 px-3 py-2 font-mono text-[11px] text-red-500",
442
+ className: "mt-2 overflow-auto rounded-lg bg-(--theme-error-muted) px-3 py-2 font-mono text-[11px] text-(--theme-error-text)",
443
443
  children: stripAnsi((output.traceback ?? []).join("\n"))
444
444
  });
445
445
  const plain = output.data?.["text/plain"];
@@ -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
@@ -80,7 +80,7 @@ function FileRow({ node, icon, rowId, rowIndex, active, checked, canPreview, get
80
80
  id: rowId,
81
81
  "aria-rowindex": rowIndex,
82
82
  "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"),
83
+ 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"),
84
84
  onClick: handleRowClick,
85
85
  style: { height: fileRowHeight(node) },
86
86
  ...rowContextMenuProps(openRowMenu, getItems),
@@ -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";
@@ -73,13 +74,43 @@ function Spacer({ height }) {
73
74
  /** A nested overflow container stealing the scroll leaves the virtualizer at
74
75
  * scrollTop 0 forever, rendering only the first window. */
75
76
  function useNestedScrollerWarning(scrollEl, count) {
76
- useEffect(() => {}, [scrollEl, count]);
77
+ useEffect(() => {
78
+ if (!isDev() || !scrollEl || count <= 100) return;
79
+ 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.");
80
+ }, [scrollEl, count]);
81
+ }
82
+ /** Pitch per kind, from the first adjacent same-kind pair: a mixed pair says
83
+ * nothing about either one. */
84
+ function measurePitches(rows) {
85
+ const pitches = /* @__PURE__ */ new Map();
86
+ rows.forEach((row, i) => {
87
+ const next = rows[i + 1];
88
+ if (next && next.kind === row.kind && !pitches.has(row.kind)) pitches.set(row.kind, next.top - row.top);
89
+ });
90
+ return pitches;
77
91
  }
78
92
  /** The constants must equal the row *pitch*, not the row's own height —
79
93
  * under border-collapse the shared border sits between rows. A 1px error
80
94
  * compounds to 10,000px across a 10k-row directory. */
81
95
  function useRowPitchAudit(scrollEl, nodes, items) {
82
- useEffect(() => {}, [
96
+ useEffect(() => {
97
+ if (!isDev() || !scrollEl || items.length < 3) return;
98
+ const rows = [...scrollEl.querySelectorAll("tbody tr")].filter((row) => !row.hasAttribute("aria-hidden")).map((row, i) => {
99
+ const node = nodes[items[i]?.index ?? -1];
100
+ return node ? {
101
+ kind: isTwoLineRow(node) ? "tall" : "short",
102
+ top: row.getBoundingClientRect().top
103
+ } : null;
104
+ }).filter((row) => row !== null);
105
+ const expected = {
106
+ short: 45,
107
+ tall: ROW_HEIGHT_TALL
108
+ };
109
+ for (const [kind, measured] of measurePitches(rows)) if (measured > 0 && Math.abs(measured - expected[kind]) > .5) {
110
+ const name = `ROW_HEIGHT_${kind.toUpperCase()}`;
111
+ 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.`);
112
+ }
113
+ }, [
83
114
  scrollEl,
84
115
  nodes,
85
116
  items
@@ -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";
@@ -16,6 +17,7 @@ function FileTable({ nodes, checkedItems, allSelected, someSelected, onSelectAll
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,