@workbench-kit/react 0.0.2-prototype.0.2.4 → 0.0.2-prototype.0.2.41

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 (221) hide show
  1. package/README.md +172 -0
  2. package/package.json +38 -21
  3. package/src/index.ts +74 -34
  4. package/src/jdw/JsonCodeEditorPane.tsx +7 -6
  5. package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +1 -3
  6. package/src/jdw/createBuiltinJdwRegistry.ts +1 -4
  7. package/src/jdw/cssRenderBackend.tsx +1 -4
  8. package/src/jdw/fixtures/jdw-fixtures.ts +0 -15
  9. package/src/json-config/JsonConfigWorkbench.tsx +1 -1
  10. package/src/layout/index.ts +11 -2
  11. package/src/layout/panel/Panel.tsx +0 -3
  12. package/src/layout/panel/index.ts +0 -2
  13. package/src/layout/sidebar/SideBarTree.tsx +301 -0
  14. package/src/layout/sidebar/SidebarActionIconBar.tsx +3 -3
  15. package/src/layout/sidebar/WorkbenchSidebarActions.tsx +1 -1
  16. package/src/layout/sidebar/index.ts +13 -0
  17. package/src/modal/Modal.tsx +55 -25
  18. package/src/modal/ModalTitlebar.tsx +8 -8
  19. package/src/modal/modalPosition.ts +3 -6
  20. package/src/modal/useModalFocusTrap.ts +142 -0
  21. package/src/modal/useModalWindowFrame.ts +2 -2
  22. package/src/overlay/ContextMenu.tsx +133 -21
  23. package/src/overlay/context-menu-density.css +22 -0
  24. package/src/overlay/context-menu-item.ts +17 -0
  25. package/src/overlay/context-menu.css +6 -2
  26. package/src/overlay/index.ts +6 -1
  27. package/src/overlay/measureAnchoredOverlayPanel.ts +4 -6
  28. package/src/overlay/pointerPassthroughRegion.ts +17 -1
  29. package/src/overlay/usePointerPassthroughRegion.ts +35 -6
  30. package/src/primitives/absolute-box/AbsoluteBox.tsx +4 -4
  31. package/src/primitives/app-icon/AppIcon.tsx +33 -0
  32. package/src/primitives/app-icon/app-icon.css +28 -0
  33. package/src/primitives/button/Button.tsx +4 -4
  34. package/src/primitives/catalog-browse-card/CatalogBrowseCard.tsx +6 -6
  35. package/src/primitives/catalog-browse-pane/CatalogBrowsePane.tsx +39 -32
  36. package/src/primitives/catalog-browse-pane/catalog-browse-pane.css +4 -0
  37. package/src/primitives/catalog-filter-overlay/CatalogFilterOverlay.tsx +2 -2
  38. package/src/primitives/checkbox/Checkbox.tsx +1 -1
  39. package/src/primitives/chip/Chip.tsx +3 -12
  40. package/src/primitives/chip/index.ts +2 -2
  41. package/src/primitives/codicon/Codicon.tsx +1 -1
  42. package/src/primitives/file-icon/FileIcon.tsx +1 -1
  43. package/src/primitives/icon-button/IconButton.tsx +1 -1
  44. package/src/primitives/index.ts +7 -2
  45. package/src/primitives/library-detail-layout/LibraryDetailLayout.tsx +123 -9
  46. package/src/primitives/library-detail-layout/library-detail-layout.css +57 -0
  47. package/src/primitives/library-detail-layout/resolve-hero-cover-media.ts +80 -0
  48. package/src/primitives/library-facet-filter-panel/LibraryFacetFilterPanel.tsx +12 -12
  49. package/src/primitives/library-facet-filter-strip/LibraryFacetFilterStrip.tsx +4 -4
  50. package/src/primitives/number-input/NumberInput.tsx +5 -5
  51. package/src/primitives/primitives.css +1 -0
  52. package/src/primitives/record-media-hero/RecordMediaHero.tsx +10 -6
  53. package/src/primitives/scroll-area/ScrollArea.tsx +5 -5
  54. package/src/primitives/scroll-area-infinite-load/useScrollAreaInfiniteLoad.ts +5 -5
  55. package/src/primitives/searchable-multi-select/SearchableMultiSelect.tsx +7 -7
  56. package/src/primitives/select/Select.tsx +1 -13
  57. package/src/primitives/text-area/TextArea.tsx +1 -1
  58. package/src/primitives/text-input/TextInput.tsx +1 -1
  59. package/src/primitives/workbench-media-slot/WorkbenchMediaPlaceholder.tsx +2 -2
  60. package/src/primitives/workbench-media-slot/WorkbenchMediaSlot.tsx +16 -10
  61. package/src/primitives/workbench-thumbnail/WorkbenchThumbnail.tsx +4 -4
  62. package/src/scrollbars.css +2 -6
  63. package/src/styles/core.css +16 -0
  64. package/src/styles/foundation.css +4 -0
  65. package/src/styles/overlay.css +2 -0
  66. package/src/styles.css +1 -15
  67. package/src/utils/clamp.ts +4 -0
  68. package/src/utils/normalizeKeyToken.ts +13 -0
  69. package/src/utils/readNumber.ts +4 -0
  70. package/src/widget-tree/WidgetSourceEditor.tsx +0 -14
  71. package/src/widget-tree/WidgetTreeCanvasPreview.tsx +3 -10
  72. package/src/widget-tree/WidgetTreeLab.tsx +2 -23
  73. package/src/widget-tree/WidgetTreeSidePanel.tsx +0 -5
  74. package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +2 -2
  75. package/src/widget-tree/index.ts +1 -5
  76. package/src/workbench/auth/WorkbenchLoginView.tsx +1 -2
  77. package/src/workbench/auth/auth-flows.css +14 -0
  78. package/src/workbench/chat/ChatCommandProposalCard.tsx +4 -4
  79. package/src/workbench/chat/ChatConversationBar.tsx +3 -3
  80. package/src/workbench/chat/ChatHistoryMenu.tsx +1 -1
  81. package/src/workbench/chat/ChatMessageCollapsible.tsx +4 -0
  82. package/src/workbench/chat/ChatMessageItem.tsx +68 -10
  83. package/src/workbench/chat/ChatMessageList.tsx +1 -1
  84. package/src/workbench/chat/ChatPanel.tsx +32 -23
  85. package/src/workbench/chat/ChatPhasedRunProgress.tsx +86 -13
  86. package/src/workbench/chat/ChatRenameDialog.tsx +1 -1
  87. package/src/workbench/chat/chat-conversation.css +2 -2
  88. package/src/workbench/chat/chat-ui.css +8 -0
  89. package/src/workbench/chat/chatConversation.ts +2 -2
  90. package/src/workbench/chat/chatMessageMeta.tsx +4 -4
  91. package/src/workbench/chat/index.ts +1 -0
  92. package/src/workbench/chat/types.ts +7 -7
  93. package/src/workbench/chat/useCancelRuntimeOnUnmount.ts +4 -4
  94. package/src/workbench/chat/useChatPrependPagination.ts +10 -10
  95. package/src/workbench/chat/useSlashCommandSuggest.ts +1 -1
  96. package/src/workbench/{WorkbenchModalPortal.tsx → chrome/WorkbenchModalPortal.tsx} +1 -1
  97. package/src/workbench/chrome/workbench-chrome.css +4 -0
  98. package/src/workbench/chrome/workbench-layout-regions.css +13 -0
  99. package/src/workbench/chrome/workbench-shell-titlebar.css +0 -8
  100. package/src/workbench/{CommandGroupShell.tsx → commands/CommandGroupShell.tsx} +2 -2
  101. package/src/workbench/{CommandList.tsx → commands/CommandList.tsx} +6 -5
  102. package/src/workbench/{CommandPalette.tsx → commands/CommandPalette.tsx} +16 -42
  103. package/src/workbench/{ConfirmationFlow.tsx → commands/ConfirmationFlow.tsx} +5 -5
  104. package/src/workbench/{ShortcutCommandBridge.tsx → commands/ShortcutCommandBridge.tsx} +17 -13
  105. package/src/workbench/commands/WorkbenchQuickOpen.tsx +462 -0
  106. package/src/workbench/{command-model.ts → commands/command-model.ts} +5 -1
  107. package/src/workbench/commands/command-ui.ts +3 -0
  108. package/src/workbench/{commands.ts → commands/commands.ts} +45 -1
  109. package/src/workbench/commands/createWorkspaceFilesQuickOpenProvider.ts +86 -0
  110. package/src/workbench/commands/quick-open-model.ts +66 -0
  111. package/src/workbench/{WorkbenchEditorTabs.tsx → editor/WorkbenchEditorTabs.tsx} +13 -3
  112. package/src/workbench/{editor-tabs-dnd.ts → editor/editor-tabs-dnd.ts} +1 -1
  113. package/src/workbench/{editorTabContextMenu.ts → editor/editorTabContextMenu.ts} +43 -4
  114. package/src/workbench/{useEditorTabsStripDnd.ts → editor/useEditorTabsStripDnd.ts} +1 -1
  115. package/src/workbench/{useWorkbenchEditorTabContextMenu.tsx → editor/useWorkbenchEditorTabContextMenu.tsx} +16 -2
  116. package/src/workbench/editor-tabs/index.ts +10 -9
  117. package/src/workbench/index.ts +114 -74
  118. package/src/workbench/management/CommandManagementSidebar.tsx +16 -30
  119. package/src/workbench/management/ExtensionManagementPanel.tsx +84 -46
  120. package/src/workbench/management/ExtensionManagementSidebar.tsx +79 -45
  121. package/src/workbench/management/IntegrationCommandActions.tsx +8 -1
  122. package/src/workbench/management/IntegrationSettingsSurface.tsx +2 -5
  123. package/src/workbench/management/IntegrationsShell.tsx +1 -1
  124. package/src/workbench/management/KeybindingCaptureField.tsx +1 -9
  125. package/src/workbench/management/WorkbenchDialogFrame.tsx +2 -2
  126. package/src/workbench/management/WorkbenchNotice.tsx +17 -12
  127. package/src/workbench/management/createWorkbenchNotify.tsx +88 -0
  128. package/src/workbench/management/extension-install-approval.ts +72 -0
  129. package/src/workbench/management/extension-management-filters.ts +38 -0
  130. package/src/workbench/management/index.ts +18 -3
  131. package/src/workbench/management/management.css +28 -0
  132. package/src/workbench/management/types.ts +23 -1
  133. package/src/workbench/management/useWorkbenchNotify.ts +10 -0
  134. package/src/workbench/{MarkdownPreview.tsx → markdown/MarkdownPreview.tsx} +22 -7
  135. package/src/workbench/markdown/markdownRemarkPlugins.ts +12 -0
  136. package/src/workbench/markdown/sanitizeMarkdownHref.ts +29 -0
  137. package/src/workbench/schema/workbenchDocumentRenderer.tsx +48 -121
  138. package/src/workbench/settings/StructuredDataForm.tsx +1 -4
  139. package/src/workbench/settings/StructuredDataSchemaPanel.tsx +39 -44
  140. package/src/workbench/settings/sectionedPanelScrollSpy.ts +0 -81
  141. package/src/workbench/settings/structuredDataFormModel.ts +2 -1
  142. package/src/workbench/settings/structuredDataSchema.ts +24 -13
  143. package/src/workbench/settings/structuredDataSchemaSection.ts +4 -0
  144. package/src/workbench/settings/structuredDataSchemaTable.ts +5 -2
  145. package/src/workbench/{ActivityBar.tsx → shell/ActivityBar.tsx} +8 -12
  146. package/src/workbench/{ArtifactShell.tsx → shell/ArtifactShell.tsx} +25 -25
  147. package/src/workbench/{SplitView.tsx → shell/SplitView.tsx} +3 -3
  148. package/src/workbench/{StatusBar.tsx → shell/StatusBar.tsx} +8 -8
  149. package/src/workbench/{StructuredArtifactEditor.tsx → shell/StructuredArtifactEditor.tsx} +9 -9
  150. package/src/workbench/{Timeline.tsx → shell/Timeline.tsx} +77 -20
  151. package/src/workbench/{WorkbenchCanvasShell.defaults.ts → shell/WorkbenchCanvasShell.defaults.ts} +1 -1
  152. package/src/workbench/{WorkbenchCanvasShell.model.ts → shell/WorkbenchCanvasShell.model.ts} +5 -5
  153. package/src/workbench/{WorkbenchCanvasShell.tsx → shell/WorkbenchCanvasShell.tsx} +6 -6
  154. package/src/workbench/{WorkbenchDesktopTitleBar.tsx → shell/WorkbenchDesktopTitleBar.tsx} +15 -15
  155. package/src/workbench/{WorkbenchShell.tsx → shell/WorkbenchShell.tsx} +45 -6
  156. package/src/workbench/{WorkbenchShellTitleBarLayoutControls.tsx → shell/WorkbenchShellTitleBarLayoutControls.tsx} +16 -19
  157. package/src/workbench/{WorkbenchStandaloneShell.tsx → shell/WorkbenchStandaloneShell.tsx} +15 -22
  158. package/src/workbench/{WorkbenchViewEditor.tsx → shell/WorkbenchViewEditor.tsx} +7 -7
  159. package/src/workbench/{WorkbenchViewSidebar.tsx → shell/WorkbenchViewSidebar.tsx} +7 -7
  160. package/src/workbench/{WorkbenchWindowChromeControls.tsx → shell/WorkbenchWindowChromeControls.tsx} +18 -15
  161. package/src/workbench/shell/activity-bar.css +47 -0
  162. package/src/workbench/{activityBarOrder.ts → shell/activityBarOrder.ts} +2 -1
  163. package/src/workbench/{shell.ts → shell/shell.ts} +20 -13
  164. package/src/workbench/{shellViewModel.ts → shell/shellViewModel.ts} +3 -3
  165. package/src/workbench/{standalone.ts → shell/standalone.ts} +14 -2
  166. package/src/workbench/{useDeferredWorkbenchMount.ts → shell/useDeferredWorkbenchMount.ts} +3 -3
  167. package/src/workbench/{useWorkbenchAppearanceDocumentSync.tsx → shell/useWorkbenchAppearanceDocumentSync.tsx} +1 -1
  168. package/src/workbench/{useWorkbenchSidebarActionBarDnd.ts → shell/useWorkbenchSidebarActionBarDnd.ts} +1 -1
  169. package/src/workbench/{useWorkbenchSidebarViewPlacementDropZone.ts → shell/useWorkbenchSidebarViewPlacementDropZone.ts} +2 -2
  170. package/src/workbench/{workbenchSidebarLayoutContext.ts → shell/workbenchSidebarLayoutContext.ts} +0 -6
  171. package/src/workbench/{workbenchViewRouteState.ts → shell/workbenchViewRouteState.ts} +4 -4
  172. package/src/workbench/{WorkbenchThemeProvider.tsx → theme/WorkbenchThemeProvider.tsx} +4 -4
  173. package/src/workbench/{theme.ts → theme/theme.ts} +1 -1
  174. package/src/workbench/{themePresets.ts → theme/themePresets.ts} +39 -64
  175. package/src/workbench/views/command-list-density.css +36 -0
  176. package/src/workbench/views/command-list.css +2 -0
  177. package/src/workbench/views/extensions-sidebar.css +2 -0
  178. package/src/workbench/views/view-hosts.css +3 -3
  179. package/src/workbench/workspace/WorkspaceEditorPanel.tsx +1 -1
  180. package/src/workbench/workspace/WorkspaceExplorer.tsx +105 -4
  181. package/src/workbench/workspace/createVirtualWorkspaceExplorerPort.ts +1 -14
  182. package/src/workbench/workspace/workspaceExplorerKeyboard.ts +109 -0
  183. package/src/workbench/workspace/workspaceJsonDiagnostics.ts +6 -5
  184. package/src/workbench-menu-surfaces.css +3 -57
  185. package/src/brand/TilepaperAppIcon.tsx +0 -181
  186. package/src/brand/index.ts +0 -2
  187. package/src/brand/tilepaper-app-icon-geometry.ts +0 -201
  188. package/src/primitives/absolute-box/index.ts +0 -1
  189. package/src/primitives/catalog-browse-card/index.ts +0 -1
  190. package/src/primitives/catalog-browse-pane/index.ts +0 -3
  191. package/src/primitives/catalog-filter-overlay/index.ts +0 -1
  192. package/src/primitives/codicon/index.ts +0 -1
  193. package/src/primitives/external-link-button/index.ts +0 -1
  194. package/src/primitives/library-detail-layout/index.ts +0 -1
  195. package/src/primitives/library-facet-filter-panel/index.ts +0 -1
  196. package/src/primitives/library-facet-filter-strip/index.ts +0 -1
  197. package/src/primitives/panel-loading/index.ts +0 -1
  198. package/src/primitives/scroll-area-infinite-load/index.ts +0 -2
  199. package/src/primitives/workbench-thumbnail/index.ts +0 -1
  200. package/src/test-utils/workbenchMonacoMock.tsx +0 -37
  201. package/src/workbench/jdw/jdw-editors.css +0 -4
  202. package/src/workbench/jdw/jdw.css +0 -2
  203. package/src/workbench/management/integration-command-action.ts +0 -25
  204. package/src/workbench/markdownRemarkPlugins.ts +0 -4
  205. package/src/workbench/workbench-chrome.css +0 -4
  206. /package/src/workbench/{WorkbenchPlatformContext.tsx → chrome/WorkbenchPlatformContext.tsx} +0 -0
  207. /package/src/workbench/{workbenchModalViewState.ts → chrome/workbenchModalViewState.ts} +0 -0
  208. /package/src/workbench/{workbenchOverlaysContext.tsx → chrome/workbenchOverlaysContext.tsx} +0 -0
  209. /package/src/workbench/{workbenchPlatformChrome.ts → chrome/workbenchPlatformChrome.ts} +0 -0
  210. /package/src/workbench/{command-execution-policy.ts → commands/command-execution-policy.ts} +0 -0
  211. /package/src/workbench/{keyboard.ts → commands/keyboard.ts} +0 -0
  212. /package/src/workbench/{workbenchContextMenu.ts → commands/workbenchContextMenu.ts} +0 -0
  213. /package/src/workbench/{appearanceLabels.ts → shell/appearanceLabels.ts} +0 -0
  214. /package/src/workbench/{hardReset.ts → shell/hardReset.ts} +0 -0
  215. /package/src/workbench/{shellPresets.ts → shell/shellPresets.ts} +0 -0
  216. /package/src/workbench/{shellState.ts → shell/shellState.ts} +0 -0
  217. /package/src/workbench/{sidebarViewPlacementDnd.ts → shell/sidebarViewPlacementDnd.ts} +0 -0
  218. /package/src/workbench/{status.ts → shell/status.ts} +0 -0
  219. /package/src/workbench/{useWorkbenchSidebarLayout.tsx → shell/useWorkbenchSidebarLayout.tsx} +0 -0
  220. /package/src/workbench/{useWorkbenchStandaloneShellStateSync.tsx → shell/useWorkbenchStandaloneShellStateSync.tsx} +0 -0
  221. /package/src/workbench/{workbenchStandaloneShellReactContext.tsx → shell/workbenchStandaloneShellReactContext.tsx} +0 -0
@@ -0,0 +1,462 @@
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useId,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ type ComponentPropsWithRef,
9
+ type KeyboardEvent,
10
+ type ReactNode,
11
+ } from 'react';
12
+ import { useModalFocusTrap } from '../../modal/useModalFocusTrap';
13
+ import { Button } from '../../primitives/button';
14
+ import { EmptyState } from '../../primitives/empty-state';
15
+ import { IconButton } from '../../primitives/icon-button';
16
+ import { TextInput } from '../../primitives/text-input';
17
+ import { cxCodicon } from '../../utils/codicon';
18
+ import { cx } from '../../utils/cx';
19
+ import {
20
+ DEFAULT_QUICK_OPEN_SEARCH_DEBOUNCE_MS,
21
+ getNextQuickOpenItemIndex,
22
+ isQuickOpenItemSelectable,
23
+ type QuickOpenItem,
24
+ type QuickOpenProvider,
25
+ type QuickOpenSelectContext,
26
+ } from './quick-open-model';
27
+
28
+ interface WorkbenchQuickOpenKeyEvent {
29
+ key: string;
30
+ preventDefault: () => void;
31
+ }
32
+
33
+ interface QuickOpenResult {
34
+ item: QuickOpenItem;
35
+ key: string;
36
+ provider: QuickOpenProvider;
37
+ }
38
+
39
+ function useControllableQuery({
40
+ defaultQuery = '',
41
+ query,
42
+ onQueryChange,
43
+ }: {
44
+ defaultQuery?: string | undefined;
45
+ query?: string | undefined;
46
+ onQueryChange?: ((query: string) => void) | undefined;
47
+ }) {
48
+ const [uncontrolledQuery, setUncontrolledQuery] = useState(defaultQuery);
49
+ const resolvedQuery = query ?? uncontrolledQuery;
50
+
51
+ const setQuery = (nextQuery: string) => {
52
+ if (query === undefined) {
53
+ setUncontrolledQuery(nextQuery);
54
+ }
55
+ onQueryChange?.(nextQuery);
56
+ };
57
+
58
+ return [resolvedQuery, setQuery] as const;
59
+ }
60
+
61
+ export interface WorkbenchQuickOpenProps extends Omit<
62
+ ComponentPropsWithRef<'div'>,
63
+ 'children' | 'onSelect' | 'title'
64
+ > {
65
+ activeItemId?: string | undefined;
66
+ closeLabel?: string | undefined;
67
+ debounceMs?: number | undefined;
68
+ defaultQuery?: string | undefined;
69
+ emptyLabel?: ReactNode | undefined;
70
+ onActiveItemChange?: ((itemId: string) => void) | undefined;
71
+ onClose: () => void;
72
+ onProviderError?: ((error: unknown, provider: QuickOpenProvider) => void) | undefined;
73
+ onQueryChange?: ((query: string) => void) | undefined;
74
+ onSelectItem?: ((item: QuickOpenItem, context: QuickOpenSelectContext) => void) | undefined;
75
+ open?: boolean | undefined;
76
+ placeholder?: string | undefined;
77
+ /** Restrict search to one provider. When omitted, all providers contribute. */
78
+ providerId?: string | undefined;
79
+ providers: readonly QuickOpenProvider[];
80
+ query?: string | undefined;
81
+ restoreFocusOnClose?: boolean | undefined;
82
+ title?: ReactNode | undefined;
83
+ }
84
+
85
+ export function WorkbenchQuickOpen({
86
+ activeItemId,
87
+ className,
88
+ closeLabel = 'Close Quick Open',
89
+ debounceMs = DEFAULT_QUICK_OPEN_SEARCH_DEBOUNCE_MS,
90
+ defaultQuery,
91
+ emptyLabel = 'No matching files',
92
+ onActiveItemChange,
93
+ onClose,
94
+ onProviderError,
95
+ onQueryChange,
96
+ onSelectItem,
97
+ open = true,
98
+ placeholder = 'Search files by name',
99
+ providerId,
100
+ providers,
101
+ query,
102
+ restoreFocusOnClose = true,
103
+ title = 'Quick Open',
104
+ ...props
105
+ }: WorkbenchQuickOpenProps) {
106
+ const titleId = useId();
107
+ const listId = useId();
108
+ const dialogRef = useRef<HTMLDivElement>(null);
109
+ const inputRef = useRef<HTMLInputElement>(null);
110
+ const [uncontrolledActiveResultKey, setUncontrolledActiveResultKey] = useState<string>();
111
+ const [results, setResults] = useState<QuickOpenResult[]>([]);
112
+ const [searching, setSearching] = useState(false);
113
+ const onProviderErrorRef = useRef(onProviderError);
114
+ const [resolvedQuery, setResolvedQuery] = useControllableQuery({
115
+ defaultQuery,
116
+ onQueryChange,
117
+ query,
118
+ });
119
+
120
+ useEffect(() => {
121
+ onProviderErrorRef.current = onProviderError;
122
+ }, [onProviderError]);
123
+
124
+ const activeProviders = useMemo(() => {
125
+ if (providerId) {
126
+ const provider = providers.find((candidate) => candidate.id === providerId);
127
+ return provider ? [provider] : [];
128
+ }
129
+ return providers;
130
+ }, [providerId, providers]);
131
+
132
+ const items = useMemo(() => results.map(({ item }) => item), [results]);
133
+ const controlledActiveResult = activeItemId
134
+ ? results.find(({ item }) => item.id === activeItemId && isQuickOpenItemSelectable(item))
135
+ : undefined;
136
+ const uncontrolledActiveResult = results.find(
137
+ ({ item, key }) => key === uncontrolledActiveResultKey && isQuickOpenItemSelectable(item),
138
+ );
139
+ const activeResult =
140
+ controlledActiveResult ??
141
+ uncontrolledActiveResult ??
142
+ results.find(({ item }) => isQuickOpenItemSelectable(item));
143
+ const resolvedActiveResultKey = activeResult?.key;
144
+ const activeIndex = activeResult ? results.indexOf(activeResult) : -1;
145
+
146
+ const updateActiveItem = useCallback(
147
+ (result: QuickOpenResult | undefined) => {
148
+ if (!result) return;
149
+ if (activeItemId === undefined) {
150
+ setUncontrolledActiveResultKey(result.key);
151
+ }
152
+ onActiveItemChange?.(result.item.id);
153
+ },
154
+ [activeItemId, onActiveItemChange],
155
+ );
156
+
157
+ useModalFocusTrap({
158
+ enabled: open,
159
+ containerRef: dialogRef,
160
+ initialFocusRef: inputRef,
161
+ onClose,
162
+ restoreFocusOnClose,
163
+ });
164
+
165
+ useEffect(() => {
166
+ if (!open || activeProviders.length === 0) {
167
+ setResults([]);
168
+ setSearching(false);
169
+ return undefined;
170
+ }
171
+
172
+ const controller = new AbortController();
173
+ let cancelled = false;
174
+ const runSearch = async () => {
175
+ setResults([]);
176
+ setSearching(true);
177
+ const providerResults = activeProviders.map<QuickOpenResult[] | undefined>(() => undefined);
178
+ const publishAvailableResults = () => {
179
+ setResults(providerResults.flatMap((providerItems) => providerItems ?? []));
180
+ };
181
+ const searches = activeProviders.map(async (provider, providerIndex) => {
182
+ try {
183
+ const providerItems = await Promise.resolve(
184
+ provider.search(resolvedQuery, { signal: controller.signal }),
185
+ );
186
+ if (cancelled) return;
187
+
188
+ providerResults[providerIndex] = providerItems.map((item, itemIndex) => ({
189
+ item,
190
+ key: `${providerIndex}:${itemIndex}:${provider.id}:${item.id}`,
191
+ provider,
192
+ }));
193
+ publishAvailableResults();
194
+ } catch (error) {
195
+ if (cancelled) return;
196
+ providerResults[providerIndex] = [];
197
+ publishAvailableResults();
198
+ onProviderErrorRef.current?.(error, provider);
199
+ }
200
+ });
201
+
202
+ await Promise.allSettled(searches);
203
+ if (!cancelled) setSearching(false);
204
+ };
205
+
206
+ // Empty query (recent / top-level) should paint immediately; debounce typed queries.
207
+ const delay = resolvedQuery.trim() ? Math.max(0, debounceMs) : 0;
208
+ if (delay === 0) {
209
+ void runSearch();
210
+ return () => {
211
+ cancelled = true;
212
+ controller.abort();
213
+ };
214
+ }
215
+
216
+ const timer = window.setTimeout(() => {
217
+ void runSearch();
218
+ }, delay);
219
+
220
+ return () => {
221
+ cancelled = true;
222
+ controller.abort();
223
+ window.clearTimeout(timer);
224
+ };
225
+ }, [activeProviders, debounceMs, open, resolvedQuery]);
226
+
227
+ useEffect(() => {
228
+ if (!open) return;
229
+ if (activeItemId !== undefined) return;
230
+
231
+ setUncontrolledActiveResultKey((currentResultKey) => {
232
+ const currentResult = results.find(({ key }) => key === currentResultKey);
233
+ if (currentResult && isQuickOpenItemSelectable(currentResult.item)) {
234
+ return currentResultKey;
235
+ }
236
+
237
+ return results.find(({ item }) => isQuickOpenItemSelectable(item))?.key;
238
+ });
239
+ }, [activeItemId, open, results]);
240
+
241
+ const selectItem = useCallback(
242
+ (result: QuickOpenResult, index: number) => {
243
+ if (!isQuickOpenItemSelectable(result.item)) return;
244
+ onSelectItem?.(result.item, {
245
+ index,
246
+ providerId: result.provider.id,
247
+ query: resolvedQuery,
248
+ });
249
+ },
250
+ [onSelectItem, resolvedQuery],
251
+ );
252
+
253
+ const handleQuickOpenKeyDown = useCallback(
254
+ (event: WorkbenchQuickOpenKeyEvent) => {
255
+ if (
256
+ event.key !== 'ArrowDown' &&
257
+ event.key !== 'ArrowUp' &&
258
+ event.key !== 'Home' &&
259
+ event.key !== 'End' &&
260
+ event.key !== 'PageDown' &&
261
+ event.key !== 'PageUp' &&
262
+ event.key !== 'Enter'
263
+ ) {
264
+ return;
265
+ }
266
+
267
+ if (event.key === 'Enter') {
268
+ if (!activeResult) return;
269
+ event.preventDefault();
270
+ selectItem(activeResult, Math.max(activeIndex, 0));
271
+ return;
272
+ }
273
+
274
+ event.preventDefault();
275
+
276
+ const direction =
277
+ event.key === 'ArrowUp' || event.key === 'End' || event.key === 'PageUp'
278
+ ? 'previous'
279
+ : 'next';
280
+ const stepCount = event.key === 'PageDown' || event.key === 'PageUp' ? 5 : 1;
281
+ let nextIndex =
282
+ event.key === 'Home'
283
+ ? getNextQuickOpenItemIndex({
284
+ currentIndex: -1,
285
+ direction: 'next',
286
+ items,
287
+ })
288
+ : event.key === 'End'
289
+ ? getNextQuickOpenItemIndex({
290
+ currentIndex: 0,
291
+ direction: 'previous',
292
+ items,
293
+ })
294
+ : activeIndex;
295
+
296
+ if (event.key !== 'Home' && event.key !== 'End') {
297
+ for (let step = 0; step < stepCount; step += 1) {
298
+ const steppedIndex = getNextQuickOpenItemIndex({
299
+ currentIndex: nextIndex,
300
+ direction,
301
+ items,
302
+ });
303
+
304
+ if (steppedIndex < 0 || steppedIndex === nextIndex) break;
305
+ nextIndex = steppedIndex;
306
+ }
307
+ }
308
+
309
+ if (nextIndex >= 0) {
310
+ updateActiveItem(results[nextIndex]);
311
+ }
312
+ },
313
+ [activeIndex, activeResult, items, results, selectItem, updateActiveItem],
314
+ );
315
+
316
+ useEffect(() => {
317
+ if (!open) return undefined;
318
+
319
+ const onKeyDown = (event: globalThis.KeyboardEvent) => {
320
+ if (event.defaultPrevented) return;
321
+ handleQuickOpenKeyDown(event);
322
+ };
323
+
324
+ window.addEventListener('keydown', onKeyDown);
325
+ return () => {
326
+ window.removeEventListener('keydown', onKeyDown);
327
+ };
328
+ }, [handleQuickOpenKeyDown, open]);
329
+
330
+ const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
331
+ handleQuickOpenKeyDown(event);
332
+ };
333
+
334
+ useEffect(() => {
335
+ if (!resolvedActiveResultKey) return;
336
+
337
+ const activeElement = Array.from(
338
+ dialogRef.current?.querySelectorAll<HTMLElement>('[data-quick-open-id]') ?? [],
339
+ ).find((element) => element.dataset.quickOpenId === resolvedActiveResultKey);
340
+
341
+ activeElement?.scrollIntoView?.({ block: 'nearest' });
342
+ }, [resolvedActiveResultKey, results]);
343
+
344
+ if (!open) return null;
345
+
346
+ return (
347
+ <div className="ui-workbench-command-palette-overlay" onClick={onClose}>
348
+ <div
349
+ aria-labelledby={titleId}
350
+ aria-modal="true"
351
+ className={cx('ui-workbench-command-palette', className)}
352
+ data-testid="workbench-quick-open"
353
+ role="dialog"
354
+ {...props}
355
+ ref={dialogRef}
356
+ onClick={(event) => event.stopPropagation()}
357
+ >
358
+ <div className="ui-workbench-command-palette__header">
359
+ <span id={titleId} className="ui-workbench-command-palette__title">
360
+ {title}
361
+ </span>
362
+ <IconButton
363
+ className="ui-workbench-command-palette__close"
364
+ icon="codicon-close"
365
+ label={closeLabel}
366
+ onClick={onClose}
367
+ />
368
+ </div>
369
+ <div className="ui-workbench-command-palette__search">
370
+ <i aria-hidden="true" className="codicon codicon-search" />
371
+ <TextInput
372
+ ref={inputRef}
373
+ aria-activedescendant={
374
+ activeResult ? `${listId}-${activeResult.key.replace(/[^\w-]/g, '_')}` : undefined
375
+ }
376
+ aria-controls={listId}
377
+ aria-label={placeholder}
378
+ className="ui-workbench-command-palette__input"
379
+ controlWidth="full"
380
+ placeholder={placeholder}
381
+ type="search"
382
+ value={resolvedQuery}
383
+ onValueChange={setResolvedQuery}
384
+ onKeyDown={handleKeyDown}
385
+ />
386
+ </div>
387
+ <div
388
+ id={listId}
389
+ aria-busy={searching || undefined}
390
+ aria-label="Quick Open results"
391
+ className={cx('ui-workbench-command-list', 'ui-workbench-scrollbar')}
392
+ role="listbox"
393
+ >
394
+ {results.length === 0 ? (
395
+ <EmptyState compact icon="codicon-search">
396
+ {emptyLabel}
397
+ </EmptyState>
398
+ ) : (
399
+ results.map((result, index) => {
400
+ const { item, key, provider } = result;
401
+ const active = key === resolvedActiveResultKey;
402
+ const itemDomId = `${listId}-${key.replace(/[^\w-]/g, '_')}`;
403
+ const descriptionId = item.description ? `${itemDomId}-description` : undefined;
404
+
405
+ return (
406
+ <Button
407
+ key={key}
408
+ id={itemDomId}
409
+ aria-describedby={descriptionId}
410
+ aria-selected={active}
411
+ className="ui-workbench-command-item"
412
+ data-active={active ? 'true' : undefined}
413
+ data-quick-open-id={key}
414
+ disabled={item.disabled}
415
+ role="option"
416
+ onClick={() => selectItem(result, index)}
417
+ onMouseEnter={() => updateActiveItem(result)}
418
+ >
419
+ <span className="ui-workbench-command-item__icon">
420
+ {item.icon ? <i aria-hidden="true" className={cxCodicon(item.icon)} /> : null}
421
+ </span>
422
+ <span className="ui-workbench-command-item__content">
423
+ <span className="ui-workbench-command-item__label">{item.label}</span>
424
+ {item.description ? (
425
+ <span id={descriptionId} className="ui-workbench-command-item__description">
426
+ {item.description}
427
+ </span>
428
+ ) : null}
429
+ </span>
430
+ <span className="ui-workbench-command-item__meta">
431
+ {item.detail ? (
432
+ <span className="ui-workbench-command-item__category">{item.detail}</span>
433
+ ) : null}
434
+ <span className="ui-workbench-command-item__category">{provider.label}</span>
435
+ </span>
436
+ </Button>
437
+ );
438
+ })
439
+ )}
440
+ </div>
441
+ </div>
442
+ </div>
443
+ );
444
+ }
445
+
446
+ export type {
447
+ QuickOpenItem,
448
+ QuickOpenProvider,
449
+ QuickOpenSearchContext,
450
+ QuickOpenSelectContext,
451
+ } from './quick-open-model';
452
+ export {
453
+ DEFAULT_QUICK_OPEN_SEARCH_DEBOUNCE_MS,
454
+ getNextQuickOpenItemIndex,
455
+ isQuickOpenItemSelectable,
456
+ } from './quick-open-model';
457
+ export {
458
+ WORKSPACE_FILES_QUICK_OPEN_PROVIDER_ID,
459
+ createWorkspaceFilesQuickOpenProvider,
460
+ resolveQuickOpenItemPath,
461
+ } from './createWorkspaceFilesQuickOpenProvider';
462
+ export type { CreateWorkspaceFilesQuickOpenProviderOptions } from './createWorkspaceFilesQuickOpenProvider';
@@ -1,6 +1,10 @@
1
1
  import type { CommandMenuItem, ResolvedCommandMenuCommandItem } from '@workbench-kit/platform';
2
2
 
3
- import { getWorkbenchStatusLabel, isWorkbenchStatusDisabled, type WorkbenchStatus } from './status';
3
+ import {
4
+ getWorkbenchStatusLabel,
5
+ isWorkbenchStatusDisabled,
6
+ type WorkbenchStatus,
7
+ } from '../shell/status';
4
8
 
5
9
  export type WorkbenchCommandStatus = WorkbenchStatus;
6
10
 
@@ -0,0 +1,3 @@
1
+ export * from './CommandPalette';
2
+ export * from './ShortcutCommandBridge';
3
+ export * from './WorkbenchQuickOpen';
@@ -10,9 +10,10 @@ import {
10
10
  type CommandValue,
11
11
  } from '@workbench-kit/platform';
12
12
 
13
- import type { ContextMenuItem } from '../overlay/ContextMenu';
13
+ import type { ContextMenuItem } from '../../overlay/ContextMenu';
14
14
 
15
15
  export const WORKBENCH_OPEN_SETTINGS_COMMAND_ID = 'workbench.openSettings';
16
+ export const WORKBENCH_TOGGLE_FOCUS_MODE_COMMAND_ID = 'workbench.toggleFocusMode';
16
17
  export const WORKBENCH_TOGGLE_PRIMARY_SIDEBAR_COMMAND_ID = 'workbench.togglePrimarySidebar';
17
18
  export const WORKBENCH_EDITOR_SAVE_COMMAND_ID = 'editor.save';
18
19
  export const WORKBENCH_EDITOR_DISCARD_CHANGES_COMMAND_ID = 'editor.discardChanges';
@@ -22,6 +23,7 @@ export const WORKBENCH_EDITOR_SPLIT_RIGHT_COMMAND_ID = 'editor.splitRight';
22
23
  export const WORKBENCH_EDITOR_SPLIT_DOWN_COMMAND_ID = 'editor.splitDown';
23
24
  export const WORKBENCH_EDITOR_CLOSE_COMMAND_ID = 'editor.close';
24
25
  export const WORKBENCH_EDITOR_CLOSE_OTHERS_COMMAND_ID = 'editor.closeOthers';
26
+ export const WORKBENCH_EDITOR_CLOSE_TO_RIGHT_COMMAND_ID = 'editor.closeToRight';
25
27
  export const WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID = 'editor.closeAll';
26
28
  export const WORKBENCH_EDITOR_DELETE_COMMAND_ID = 'editor.delete';
27
29
  export const WORKBENCH_WORKSPACE_NEW_FILE_COMMAND_ID = 'workspace.newFile';
@@ -54,9 +56,11 @@ export interface WorkbenchShellCommandActivity<TActivityId extends string = stri
54
56
  }
55
57
 
56
58
  export interface WorkbenchShellCommandContext<TActivityId extends string = string> {
59
+ isFocusModeActive?: boolean | undefined;
57
60
  isPrimarySidebarVisible: boolean;
58
61
  openSettings: () => void;
59
62
  showActivity: (activityId: TActivityId) => void;
63
+ toggleFocusMode?: (() => void) | undefined;
60
64
  togglePrimarySidebar: () => void;
61
65
  }
62
66
 
@@ -96,6 +100,7 @@ function applyWorkbenchCommandOverrides<TContext>(
96
100
 
97
101
  export interface WorkbenchShellCommandPresetOptions<TActivityId extends string = string> {
98
102
  activities: WorkbenchShellCommandActivity<TActivityId>[];
103
+ includeFocusModeToggle?: boolean | undefined;
99
104
  includeSettings?: boolean | undefined;
100
105
  includeSidebarToggle?: boolean | undefined;
101
106
  menuSeparatorId?: string | undefined;
@@ -109,6 +114,8 @@ export interface WorkbenchShellCommandPresetOptions<TActivityId extends string =
109
114
  export interface WorkbenchEditorCommandContext {
110
115
  canCloseAll: boolean;
111
116
  canCloseOthers: boolean;
117
+ /** Optional so existing public command-context object literals remain source-compatible. */
118
+ canCloseToRight?: boolean | undefined;
112
119
  canClosePath: boolean;
113
120
  canCopyPath: boolean;
114
121
  canDeletePath: boolean;
@@ -119,6 +126,8 @@ export interface WorkbenchEditorCommandContext {
119
126
  canTogglePinned: boolean;
120
127
  closeAll: () => void;
121
128
  closeOthers: () => void;
129
+ /** Presence advertises support and makes Close to the right visible. */
130
+ closeToRight?: (() => void) | undefined;
122
131
  closePath: () => void;
123
132
  copyPath: () => void;
124
133
  deletePath: () => void;
@@ -159,6 +168,7 @@ export function getWorkbenchShowActivityCommandId(activityId: string) {
159
168
 
160
169
  export function createWorkbenchShellCommands<TActivityId extends string>({
161
170
  activities,
171
+ includeFocusModeToggle = true,
162
172
  includeSettings = true,
163
173
  includeSidebarToggle = true,
164
174
  settingsIcon = 'codicon-settings-gear',
@@ -179,6 +189,19 @@ export function createWorkbenchShellCommands<TActivityId extends string>({
179
189
 
180
190
  const shellCommands: CommandDefinition<WorkbenchShellCommandContext<TActivityId>>[] = [];
181
191
 
192
+ if (includeFocusModeToggle) {
193
+ shellCommands.push({
194
+ id: WORKBENCH_TOGGLE_FOCUS_MODE_COMMAND_ID,
195
+ icon: 'codicon-screen-full',
196
+ isEnabled: ({ toggleFocusMode }) => toggleFocusMode !== undefined,
197
+ label: ({ isFocusModeActive }) =>
198
+ isFocusModeActive ? 'Exit Focus Mode' : 'Enter Focus Mode',
199
+ run: ({ toggleFocusMode }) => toggleFocusMode?.(),
200
+ shortcut: 'Ctrl/Cmd+Shift+F11',
201
+ title: 'Toggle Focus Mode',
202
+ });
203
+ }
204
+
182
205
  if (includeSidebarToggle) {
183
206
  shellCommands.push({
184
207
  id: WORKBENCH_TOGGLE_PRIMARY_SIDEBAR_COMMAND_ID,
@@ -207,6 +230,7 @@ export function createWorkbenchShellCommands<TActivityId extends string>({
207
230
 
208
231
  export function createWorkbenchShellMenuEntries<TActivityId extends string>({
209
232
  activities,
233
+ includeFocusModeToggle = true,
210
234
  includeSettings = true,
211
235
  includeSidebarToggle = true,
212
236
  menuSeparatorId = 'workbench-shell-separator',
@@ -223,6 +247,15 @@ export function createWorkbenchShellMenuEntries<TActivityId extends string>({
223
247
  );
224
248
  const shellEntries: CommandMenuEntry<WorkbenchShellCommandContext<TActivityId>>[] = [];
225
249
 
250
+ if (includeFocusModeToggle) {
251
+ shellEntries.push(
252
+ commandMenuEntry<WorkbenchShellCommandContext<TActivityId>>(
253
+ WORKBENCH_TOGGLE_FOCUS_MODE_COMMAND_ID,
254
+ { surfaces: [WORKBENCH_COMMAND_SURFACE_ACTIVITY_BAR] },
255
+ ),
256
+ );
257
+ }
258
+
226
259
  if (includeSidebarToggle) {
227
260
  shellEntries.push(
228
261
  commandMenuEntry<WorkbenchShellCommandContext<TActivityId>>(
@@ -314,6 +347,14 @@ export function createWorkbenchEditorCommands({
314
347
  label: 'Close others',
315
348
  run: ({ closeOthers }) => closeOthers(),
316
349
  },
350
+ {
351
+ id: WORKBENCH_EDITOR_CLOSE_TO_RIGHT_COMMAND_ID,
352
+ icon: 'codicon-close-all',
353
+ isEnabled: ({ canCloseToRight, filePath }) => Boolean(filePath && canCloseToRight),
354
+ isVisible: ({ closeToRight }) => Boolean(closeToRight),
355
+ label: 'Close to the right',
356
+ run: ({ closeToRight }) => closeToRight?.(),
357
+ },
317
358
  {
318
359
  id: WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID,
319
360
  icon: 'codicon-close-all',
@@ -379,6 +420,9 @@ export function createWorkbenchStandaloneEditorTabMenuEntries(): CommandMenuEntr
379
420
  commandMenuEntry<WorkbenchEditorCommandContext>(WORKBENCH_EDITOR_CLOSE_OTHERS_COMMAND_ID, {
380
421
  surfaces: [WORKBENCH_COMMAND_SURFACE_EDITOR],
381
422
  }),
423
+ commandMenuEntry<WorkbenchEditorCommandContext>(WORKBENCH_EDITOR_CLOSE_TO_RIGHT_COMMAND_ID, {
424
+ surfaces: [WORKBENCH_COMMAND_SURFACE_EDITOR],
425
+ }),
382
426
  commandMenuEntry<WorkbenchEditorCommandContext>(WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID, {
383
427
  surfaces: [WORKBENCH_COMMAND_SURFACE_EDITOR],
384
428
  }),
@@ -0,0 +1,86 @@
1
+ import { searchWorkspaceFiles, type WorkspaceFile } from '@workbench-kit/workspace';
2
+
3
+ import type { QuickOpenItem, QuickOpenProvider } from './quick-open-model';
4
+
5
+ export const WORKSPACE_FILES_QUICK_OPEN_PROVIDER_ID = 'workspace.files' as const;
6
+
7
+ export interface CreateWorkspaceFilesQuickOpenProviderOptions {
8
+ /** Current workspace files, or a getter read on each search. */
9
+ files: readonly WorkspaceFile[] | (() => readonly WorkspaceFile[]);
10
+ id?: string | undefined;
11
+ label?: string | undefined;
12
+ /**
13
+ * When the query is empty, these paths appear first (still present in `files`).
14
+ * Remaining files follow in `searchWorkspaceFiles` order.
15
+ */
16
+ recentPaths?: readonly string[] | undefined;
17
+ }
18
+
19
+ function resolveFiles(
20
+ files: CreateWorkspaceFilesQuickOpenProviderOptions['files'],
21
+ ): readonly WorkspaceFile[] {
22
+ return typeof files === 'function' ? files() : files;
23
+ }
24
+
25
+ function toQuickOpenItem(path: string, matchedBy?: string, preview?: string): QuickOpenItem {
26
+ const segments = path.split('/');
27
+ const label = segments[segments.length - 1] || path;
28
+ const parent = segments.length > 1 ? segments.slice(0, -1).join('/') : undefined;
29
+
30
+ return {
31
+ data: { path },
32
+ description: parent,
33
+ detail: matchedBy ?? preview,
34
+ icon: 'codicon-file',
35
+ id: path,
36
+ label,
37
+ };
38
+ }
39
+
40
+ export function createWorkspaceFilesQuickOpenProvider(
41
+ options: CreateWorkspaceFilesQuickOpenProviderOptions,
42
+ ): QuickOpenProvider {
43
+ const providerId = options.id ?? WORKSPACE_FILES_QUICK_OPEN_PROVIDER_ID;
44
+ const providerLabel = options.label ?? 'Files';
45
+
46
+ return {
47
+ id: providerId,
48
+ label: providerLabel,
49
+ search(query: string) {
50
+ const files = [...resolveFiles(options.files)];
51
+ const results = searchWorkspaceFiles(files, query);
52
+ const items = results.map((result) =>
53
+ toQuickOpenItem(result.path, result.matchedBy, result.preview),
54
+ );
55
+
56
+ if (query.trim() || !options.recentPaths?.length) {
57
+ return items;
58
+ }
59
+
60
+ const recentSet = new Set(options.recentPaths);
61
+ const byPath = new Map(items.map((item) => [item.id, item]));
62
+ const recentItems: QuickOpenItem[] = [];
63
+
64
+ for (const path of options.recentPaths) {
65
+ const item = byPath.get(path);
66
+ if (item) {
67
+ recentItems.push({ ...item, detail: 'Recent' });
68
+ }
69
+ }
70
+
71
+ const rest = items.filter((item) => !recentSet.has(item.id));
72
+ return [...recentItems, ...rest];
73
+ },
74
+ };
75
+ }
76
+
77
+ export function resolveQuickOpenItemPath(item: QuickOpenItem): string | undefined {
78
+ if (typeof item.data === 'object' && item.data !== null && 'path' in item.data) {
79
+ const path = (item.data as { path?: unknown }).path;
80
+ if (typeof path === 'string' && path.length > 0) {
81
+ return path;
82
+ }
83
+ }
84
+
85
+ return item.id || undefined;
86
+ }