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

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} +3 -2
  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
@@ -9,7 +9,7 @@ import { IconButton } from '../primitives/icon-button';
9
9
  import { Toolbar } from '../primitives/toolbar';
10
10
  import { JsonCodeEditorPane, JsonConfigValidationBanner } from '../jdw/JsonCodeEditorPane.js';
11
11
  import { JdwPreview } from '../jdw/JdwPreview.js';
12
- import { SplitView } from '../workbench/SplitView';
12
+ import { SplitView } from '../workbench/shell/SplitView';
13
13
  import { WorkbenchStructuredDataSchemaPanel } from '../workbench/settings/StructuredDataSchemaPanel';
14
14
  import { type WorkbenchStructuredDataSchemaDocument } from '../workbench/settings/StructuredDataForm';
15
15
  import { type WorkspaceEditorTheme } from '../workbench/workspace/WorkspaceEditor';
@@ -2,7 +2,6 @@ export {
2
2
  FilterBar,
3
3
  FilterBarActiveChips,
4
4
  FilterBarRow,
5
- FilterChip,
6
5
  HelpText,
7
6
  Panel,
8
7
  PanelBody,
@@ -19,7 +18,6 @@ export type {
19
18
  FilterBarActiveChipsProps,
20
19
  FilterBarProps,
21
20
  FilterBarRowProps,
22
- FilterChipProps,
23
21
  HelpTextProps,
24
22
  PanelBodyProps,
25
23
  PanelFooterProps,
@@ -56,6 +54,7 @@ export {
56
54
  SideBarListItem,
57
55
  SideBarRow,
58
56
  SideBarScrollSpacer,
57
+ SideBarTree,
59
58
  SideBarViewFrame,
60
59
  SideBarViewTabStrip,
61
60
  SideBarViewTitleMenu,
@@ -68,7 +67,11 @@ export {
68
67
  WorkbenchSidebarSection,
69
68
  WorkbenchSidebarSectionHeader,
70
69
  WorkbenchSidebarSectionStack,
70
+ flattenVisibleSideBarTreeItems,
71
+ isSideBarTreeBranch,
72
+ selectSideBarTreeIds,
71
73
  sideBarTreeDepthStyle,
74
+ toggleSideBarTreeId,
72
75
  useSidebarSectionBaseDepth,
73
76
  } from './sidebar';
74
77
  export type {
@@ -77,6 +80,10 @@ export type {
77
80
  SideBarListProps,
78
81
  SideBarRowProps,
79
82
  SideBarScrollSpacerProps,
83
+ SideBarTreeItem,
84
+ SideBarTreeProps,
85
+ SideBarTreeSelectionMode,
86
+ SideBarTreeVisibleNode,
80
87
  SideBarViewFrameProps,
81
88
  SideBarViewTabDescriptor,
82
89
  SideBarViewTabStripProps,
@@ -142,6 +149,8 @@ export type {
142
149
  } from './propertyFieldFilter';
143
150
  export { WorkbenchMediaPreviewViewport } from './WorkbenchMediaPreviewViewport';
144
151
  export type { WorkbenchMediaPreviewViewportProps } from './WorkbenchMediaPreviewViewport';
152
+ export { WorkbenchPreviewCanvas } from './WorkbenchCanvas';
153
+ export type { WorkbenchPreviewCanvasProps } from './WorkbenchCanvas';
145
154
  export {
146
155
  clampPreviewViewportZoom,
147
156
  computePreviewViewportFitScale,
@@ -71,9 +71,6 @@ export function FilterBarRow({ className, ...props }: FilterBarRowProps) {
71
71
  return <div className={cx('ui-filter-bar__row', className)} {...props} />;
72
72
  }
73
73
 
74
- /** @deprecated Use `Chip` / `ChipProps` from `@workbench-kit/react` primitives. */
75
- export { FilterChip, type FilterChipProps } from '../../primitives/chip';
76
-
77
74
  export interface FilterBarActiveChipsProps extends ComponentPropsWithRef<'div'> {
78
75
  clearAllLabel?: string | undefined;
79
76
  onClearAll?: (() => void) | undefined;
@@ -2,7 +2,6 @@ export {
2
2
  FilterBar,
3
3
  FilterBarActiveChips,
4
4
  FilterBarRow,
5
- FilterChip,
6
5
  HelpText,
7
6
  Panel,
8
7
  PanelBody,
@@ -19,7 +18,6 @@ export type {
19
18
  FilterBarActiveChipsProps,
20
19
  FilterBarProps,
21
20
  FilterBarRowProps,
22
- FilterChipProps,
23
21
  HelpTextProps,
24
22
  PanelBodyProps,
25
23
  PanelFooterProps,
@@ -0,0 +1,301 @@
1
+ import {
2
+ useCallback,
3
+ useMemo,
4
+ useState,
5
+ type KeyboardEvent,
6
+ type MouseEvent,
7
+ type ReactNode,
8
+ } from 'react';
9
+ import { cxCodicon } from '../../utils/codicon';
10
+ import { cx } from '../../utils/cx';
11
+ import { SideBarList, SideBarListItem } from './SideBarViewFrame';
12
+
13
+ /**
14
+ * Controlled sidebar tree for library / provider category rows.
15
+ *
16
+ * Branch vs leaf: items with `children` (even empty) render as expandable branches;
17
+ * items without `children` are leaves. Hosts own expand/selection sets.
18
+ *
19
+ * Keyboard (default on): ArrowUp/Down move focus+selection (single mode) or focus
20
+ * (multi mode); ArrowRight expands / moves into first child; ArrowLeft collapses /
21
+ * moves to parent; Enter/Space activates selection. Virtualization and DnD are
22
+ * out of scope for v1.
23
+ */
24
+ export interface SideBarTreeItem {
25
+ readonly id: string;
26
+ readonly label: ReactNode;
27
+ readonly children?: readonly SideBarTreeItem[];
28
+ readonly disabled?: boolean;
29
+ readonly icon?: ReactNode;
30
+ }
31
+
32
+ export type SideBarTreeSelectionMode = 'single' | 'multi';
33
+
34
+ export interface SideBarTreeProps {
35
+ readonly items: readonly SideBarTreeItem[];
36
+ readonly expandedIds: ReadonlySet<string>;
37
+ readonly selectedIds: ReadonlySet<string>;
38
+ readonly onExpandedIdsChange: (next: Set<string>) => void;
39
+ readonly onSelectedIdsChange: (next: Set<string>) => void;
40
+ readonly selectionMode?: SideBarTreeSelectionMode;
41
+ /** When false, arrow / activation keys are not handled. Default true. */
42
+ readonly keyboardNavigation?: boolean;
43
+ readonly 'aria-label'?: string;
44
+ readonly className?: string;
45
+ }
46
+
47
+ export interface SideBarTreeVisibleNode {
48
+ readonly depth: number;
49
+ readonly item: SideBarTreeItem;
50
+ readonly parentId: string | null;
51
+ readonly hasChildren: boolean;
52
+ }
53
+
54
+ export function isSideBarTreeBranch(item: SideBarTreeItem): boolean {
55
+ return item.children !== undefined;
56
+ }
57
+
58
+ export function flattenVisibleSideBarTreeItems(
59
+ items: readonly SideBarTreeItem[],
60
+ expandedIds: ReadonlySet<string>,
61
+ depth = 0,
62
+ parentId: string | null = null,
63
+ ): SideBarTreeVisibleNode[] {
64
+ const rows: SideBarTreeVisibleNode[] = [];
65
+
66
+ for (const item of items) {
67
+ const hasChildren = isSideBarTreeBranch(item);
68
+ rows.push({ depth, item, parentId, hasChildren });
69
+
70
+ if (hasChildren && expandedIds.has(item.id) && item.children) {
71
+ rows.push(...flattenVisibleSideBarTreeItems(item.children, expandedIds, depth + 1, item.id));
72
+ }
73
+ }
74
+
75
+ return rows;
76
+ }
77
+
78
+ export function toggleSideBarTreeId(ids: ReadonlySet<string>, id: string): Set<string> {
79
+ const next = new Set(ids);
80
+ if (next.has(id)) {
81
+ next.delete(id);
82
+ } else {
83
+ next.add(id);
84
+ }
85
+ return next;
86
+ }
87
+
88
+ export function selectSideBarTreeIds(
89
+ current: ReadonlySet<string>,
90
+ id: string,
91
+ mode: SideBarTreeSelectionMode,
92
+ additive: boolean,
93
+ ): Set<string> {
94
+ if (mode === 'single' || !additive) {
95
+ return new Set([id]);
96
+ }
97
+ return toggleSideBarTreeId(current, id);
98
+ }
99
+
100
+ export function SideBarTree({
101
+ items,
102
+ expandedIds,
103
+ selectedIds,
104
+ onExpandedIdsChange,
105
+ onSelectedIdsChange,
106
+ selectionMode = 'single',
107
+ keyboardNavigation = true,
108
+ 'aria-label': ariaLabel = 'Sidebar tree',
109
+ className,
110
+ }: SideBarTreeProps) {
111
+ const visibleNodes = useMemo(
112
+ () => flattenVisibleSideBarTreeItems(items, expandedIds),
113
+ [expandedIds, items],
114
+ );
115
+
116
+ const [focusedId, setFocusedId] = useState<string | null>(null);
117
+
118
+ const focusIndex = useMemo(() => {
119
+ if (focusedId == null) {
120
+ return visibleNodes.length > 0 ? 0 : -1;
121
+ }
122
+ const index = visibleNodes.findIndex((node) => node.item.id === focusedId);
123
+ return index >= 0 ? index : visibleNodes.length > 0 ? 0 : -1;
124
+ }, [focusedId, visibleNodes]);
125
+
126
+ const toggleExpanded = useCallback(
127
+ (id: string) => {
128
+ onExpandedIdsChange(toggleSideBarTreeId(expandedIds, id));
129
+ },
130
+ [expandedIds, onExpandedIdsChange],
131
+ );
132
+
133
+ const selectItem = useCallback(
134
+ (id: string, additive: boolean) => {
135
+ onSelectedIdsChange(selectSideBarTreeIds(selectedIds, id, selectionMode, additive));
136
+ setFocusedId(id);
137
+ },
138
+ [onSelectedIdsChange, selectedIds, selectionMode],
139
+ );
140
+
141
+ const handleItemClick = useCallback(
142
+ (event: MouseEvent<HTMLButtonElement>, node: SideBarTreeVisibleNode) => {
143
+ if (node.item.disabled) {
144
+ return;
145
+ }
146
+
147
+ const additive = selectionMode === 'multi' && (event.metaKey || event.ctrlKey);
148
+ selectItem(node.item.id, additive);
149
+
150
+ if (node.hasChildren && !additive) {
151
+ toggleExpanded(node.item.id);
152
+ }
153
+ },
154
+ [selectItem, selectionMode, toggleExpanded],
155
+ );
156
+
157
+ const handleKeyDown = useCallback(
158
+ (event: KeyboardEvent<HTMLUListElement>) => {
159
+ if (!keyboardNavigation || visibleNodes.length === 0 || focusIndex < 0) {
160
+ return;
161
+ }
162
+
163
+ const current = visibleNodes[focusIndex];
164
+ if (!current) {
165
+ return;
166
+ }
167
+
168
+ if (event.key === 'ArrowDown') {
169
+ event.preventDefault();
170
+ const next = visibleNodes[Math.min(visibleNodes.length - 1, focusIndex + 1)];
171
+ if (!next || next.item.disabled) {
172
+ return;
173
+ }
174
+ setFocusedId(next.item.id);
175
+ if (selectionMode === 'single') {
176
+ onSelectedIdsChange(new Set([next.item.id]));
177
+ }
178
+ return;
179
+ }
180
+
181
+ if (event.key === 'ArrowUp') {
182
+ event.preventDefault();
183
+ const next = visibleNodes[Math.max(0, focusIndex - 1)];
184
+ if (!next || next.item.disabled) {
185
+ return;
186
+ }
187
+ setFocusedId(next.item.id);
188
+ if (selectionMode === 'single') {
189
+ onSelectedIdsChange(new Set([next.item.id]));
190
+ }
191
+ return;
192
+ }
193
+
194
+ if (event.key === 'ArrowRight') {
195
+ event.preventDefault();
196
+ if (!current.hasChildren || current.item.disabled) {
197
+ return;
198
+ }
199
+ if (!expandedIds.has(current.item.id)) {
200
+ toggleExpanded(current.item.id);
201
+ return;
202
+ }
203
+ const firstChild = current.item.children?.[0];
204
+ if (firstChild && !firstChild.disabled) {
205
+ setFocusedId(firstChild.id);
206
+ if (selectionMode === 'single') {
207
+ onSelectedIdsChange(new Set([firstChild.id]));
208
+ }
209
+ }
210
+ return;
211
+ }
212
+
213
+ if (event.key === 'ArrowLeft') {
214
+ event.preventDefault();
215
+ if (current.hasChildren && expandedIds.has(current.item.id)) {
216
+ toggleExpanded(current.item.id);
217
+ return;
218
+ }
219
+ if (current.parentId) {
220
+ setFocusedId(current.parentId);
221
+ if (selectionMode === 'single') {
222
+ onSelectedIdsChange(new Set([current.parentId]));
223
+ }
224
+ }
225
+ return;
226
+ }
227
+
228
+ if (event.key === 'Enter' || event.key === ' ') {
229
+ event.preventDefault();
230
+ if (current.item.disabled) {
231
+ return;
232
+ }
233
+ selectItem(current.item.id, selectionMode === 'multi' && (event.metaKey || event.ctrlKey));
234
+ if (current.hasChildren && selectionMode === 'single') {
235
+ toggleExpanded(current.item.id);
236
+ }
237
+ }
238
+ },
239
+ [
240
+ expandedIds,
241
+ focusIndex,
242
+ keyboardNavigation,
243
+ onSelectedIdsChange,
244
+ selectItem,
245
+ selectionMode,
246
+ toggleExpanded,
247
+ visibleNodes,
248
+ ],
249
+ );
250
+
251
+ return (
252
+ <SideBarList
253
+ aria-label={ariaLabel}
254
+ className={cx('ui-sidebar-tree', className)}
255
+ fill
256
+ role="tree"
257
+ onKeyDown={handleKeyDown}
258
+ >
259
+ {visibleNodes.map((node, index) => {
260
+ const { item, depth, hasChildren } = node;
261
+ const expanded = hasChildren && expandedIds.has(item.id);
262
+ const selected = selectedIds.has(item.id);
263
+ const focused = focusIndex === index;
264
+
265
+ return (
266
+ <SideBarListItem
267
+ key={item.id}
268
+ depth={depth}
269
+ disabled={item.disabled}
270
+ selected={selected}
271
+ tabIndex={focused ? 0 : -1}
272
+ wrapperProps={{
273
+ role: 'treeitem',
274
+ 'aria-level': depth + 1,
275
+ 'aria-selected': selected,
276
+ ...(hasChildren ? { 'aria-expanded': expanded } : {}),
277
+ }}
278
+ onClick={(event) => handleItemClick(event, node)}
279
+ onFocus={() => setFocusedId(item.id)}
280
+ >
281
+ <span className="workbench-tree-prefix">
282
+ {hasChildren ? (
283
+ <i
284
+ aria-hidden="true"
285
+ className={cxCodicon(
286
+ expanded ? 'chevron-down' : 'chevron-right',
287
+ 'workbench-tree-chevron',
288
+ )}
289
+ />
290
+ ) : (
291
+ <span className="workbench-tree-spacer" />
292
+ )}
293
+ {item.icon}
294
+ </span>
295
+ <span className="workbench-tree-label">{item.label}</span>
296
+ </SideBarListItem>
297
+ );
298
+ })}
299
+ </SideBarList>
300
+ );
301
+ }
@@ -12,14 +12,14 @@ import { IconButton } from '../../primitives/icon-button';
12
12
 
13
13
  import { cx } from '../../utils/cx';
14
14
 
15
- import { type ActivityBarOrientation } from '../../workbench/activityBarOrder';
15
+ import { type ActivityBarOrientation } from '../../workbench/shell/activityBarOrder';
16
16
 
17
17
  import {
18
18
  mergeWorkbenchSidebarViewPlacementDropZoneProps,
19
19
  useWorkbenchSidebarViewPlacementDropZone,
20
- } from '../../workbench/useWorkbenchSidebarViewPlacementDropZone';
20
+ } from '../../workbench/shell/useWorkbenchSidebarViewPlacementDropZone';
21
21
 
22
- import { useWorkbenchSidebarActionBarDnd } from '../../workbench/useWorkbenchSidebarActionBarDnd';
22
+ import { useWorkbenchSidebarActionBarDnd } from '../../workbench/shell/useWorkbenchSidebarActionBarDnd';
23
23
 
24
24
  import { SidebarToolbar } from './SidebarToolbar';
25
25
 
@@ -8,7 +8,7 @@ import {
8
8
  isWorkbenchStatusBusy,
9
9
  isWorkbenchStatusDisabled,
10
10
  type WorkbenchStatus,
11
- } from '../../workbench/status';
11
+ } from '../../workbench/shell/status';
12
12
  import {
13
13
  SideBarList,
14
14
  SideBarListItem,
@@ -17,6 +17,19 @@ export type {
17
17
  SideBarScrollSpacerProps,
18
18
  SideBarViewFrameProps,
19
19
  } from './SideBarViewFrame';
20
+ export {
21
+ SideBarTree,
22
+ flattenVisibleSideBarTreeItems,
23
+ isSideBarTreeBranch,
24
+ selectSideBarTreeIds,
25
+ toggleSideBarTreeId,
26
+ } from './SideBarTree';
27
+ export type {
28
+ SideBarTreeItem,
29
+ SideBarTreeProps,
30
+ SideBarTreeSelectionMode,
31
+ SideBarTreeVisibleNode,
32
+ } from './SideBarTree';
20
33
  export { SidebarActionIconBar } from './SidebarActionIconBar';
21
34
  export { SidebarSlotChrome } from './SidebarSlotChrome';
22
35
  export type { SidebarSlotChromeProps } from './SidebarSlotChrome';
@@ -1,18 +1,22 @@
1
1
  import {
2
+ useCallback,
2
3
  useId,
4
+ useRef,
3
5
  type CSSProperties,
4
6
  type FormEventHandler,
5
7
  type MouseEvent,
6
8
  type ReactNode,
9
+ type RefObject,
7
10
  } from 'react';
8
11
  import { cx } from '../utils/cx';
9
12
  import { ModalResizeHandles } from './ModalResizeHandles';
10
13
  import { ModalTitlebar } from './ModalTitlebar';
14
+ import { useModalFocusTrap } from './useModalFocusTrap';
11
15
  import { useModalWindowFrame } from './useModalWindowFrame';
12
16
  import {
13
17
  resolveWorkbenchWindowChromeDataAttributes,
14
18
  type WorkbenchWindowChromeMode,
15
- } from '../workbench/workbenchPlatformChrome';
19
+ } from '../workbench/chrome/workbenchPlatformChrome';
16
20
 
17
21
  export type { ModalBounds, ModalPosition, ModalSize } from './modalTypes';
18
22
 
@@ -20,44 +24,50 @@ export type ModalBodyLayout = 'block' | 'stack';
20
24
  export type ModalBodyPadding = 'none' | 'md' | 'lg';
21
25
 
22
26
  export interface ModalProps {
23
- chrome?: WorkbenchWindowChromeMode | undefined;
27
+ chrome?: WorkbenchWindowChromeMode;
24
28
  title: ReactNode;
25
- titleSuffix?: ReactNode | undefined;
29
+ titleSuffix?: ReactNode;
26
30
  children: ReactNode;
27
- footer?: ReactNode | undefined;
28
- className?: string | undefined;
29
- bodyClassName?: string | undefined;
30
- bodyLayout?: ModalBodyLayout | undefined;
31
- bodyPadding?: ModalBodyPadding | undefined;
32
- bodyScroll?: 'auto' | 'hidden' | undefined;
33
- closeLabel?: string | undefined;
34
- defaultHeight?: number | undefined;
35
- defaultMaximized?: boolean | undefined;
36
- defaultWidth?: number | undefined;
37
- labelledBy?: string | undefined;
38
- maximizeLabel?: string | undefined;
39
- minHeight?: number | undefined;
40
- minWidth?: number | undefined;
31
+ footer?: ReactNode;
32
+ className?: string;
33
+ bodyClassName?: string;
34
+ bodyLayout?: ModalBodyLayout;
35
+ bodyPadding?: ModalBodyPadding;
36
+ bodyScroll?: 'auto' | 'hidden';
37
+ /** When true (default), Escape calls `onClose`. */
38
+ closeOnEscape?: boolean;
39
+ closeLabel?: string;
40
+ defaultHeight?: number;
41
+ defaultMaximized?: boolean;
42
+ defaultWidth?: number;
43
+ /** Optional element to focus when the modal mounts (must be inside the dialog). */
44
+ initialFocusRef?: RefObject<HTMLElement | null>;
45
+ labelledBy?: string;
46
+ maximizeLabel?: string;
47
+ minHeight?: number;
48
+ minWidth?: number;
41
49
  onClose: () => void;
42
- onSubmit?: FormEventHandler<HTMLFormElement> | undefined;
43
- restoreLabel?: string | undefined;
50
+ onSubmit?: FormEventHandler<HTMLFormElement>;
51
+ /** When true (default), restore focus to the previously focused element on unmount. */
52
+ restoreFocusOnClose?: boolean;
53
+ restoreLabel?: string;
44
54
  }
45
55
 
46
56
  interface ModalFrameProps {
47
- bodyClassName?: string | undefined;
57
+ bodyClassName?: string;
48
58
  bodyLayout: ModalBodyLayout;
49
59
  bodyPadding: ModalBodyPadding;
50
60
  bodyScroll: 'auto' | 'hidden';
51
61
  children: ReactNode;
52
62
  dataAttrs: Record<string, string | undefined>;
53
- footer?: ReactNode | undefined;
63
+ footer?: ReactNode;
54
64
  frameRef: (node: HTMLDivElement | HTMLFormElement | null) => void;
55
65
  modalClassName: string;
56
66
  resolvedLabelledBy: string;
57
- resizeHandles?: ReactNode | undefined;
58
- style?: CSSProperties | undefined;
67
+ resizeHandles?: ReactNode;
68
+ style?: CSSProperties;
59
69
  titlebar: ReactNode;
60
- onSubmit?: FormEventHandler<HTMLFormElement> | undefined;
70
+ onSubmit?: FormEventHandler<HTMLFormElement>;
61
71
  }
62
72
 
63
73
  function ModalFrame({
@@ -133,22 +143,26 @@ export function Modal({
133
143
  bodyLayout = 'block',
134
144
  bodyPadding = 'none',
135
145
  bodyScroll = 'hidden',
146
+ closeOnEscape = true,
136
147
  closeLabel,
137
148
  defaultHeight,
138
149
  defaultMaximized = false,
139
150
  defaultWidth,
151
+ initialFocusRef,
140
152
  labelledBy,
141
153
  maximizeLabel,
142
154
  minHeight = 200,
143
155
  minWidth = 320,
144
156
  onClose,
145
157
  onSubmit,
158
+ restoreFocusOnClose = true,
146
159
  restoreLabel,
147
160
  }: ModalProps) {
148
161
  const modalClassName = cx('ui-modal', className);
149
162
  const chromeAttributes = resolveWorkbenchWindowChromeDataAttributes(chrome);
150
163
  const generatedLabelId = useId();
151
164
  const resolvedLabelledBy = labelledBy ?? generatedLabelId;
165
+ const dialogRef = useRef<HTMLElement | null>(null);
152
166
 
153
167
  const {
154
168
  assignFrameRef,
@@ -167,6 +181,22 @@ export function Modal({
167
181
  minWidth,
168
182
  });
169
183
 
184
+ const setFrameRef = useCallback(
185
+ (node: HTMLDivElement | HTMLFormElement | null) => {
186
+ dialogRef.current = node;
187
+ assignFrameRef(node);
188
+ },
189
+ [assignFrameRef],
190
+ );
191
+
192
+ useModalFocusTrap({
193
+ closeOnEscape,
194
+ containerRef: dialogRef,
195
+ initialFocusRef,
196
+ onClose,
197
+ restoreFocusOnClose,
198
+ });
199
+
170
200
  const isPositioned = bounds !== null || maximized;
171
201
 
172
202
  return (
@@ -183,7 +213,7 @@ export function Modal({
183
213
  ...(chromeAttributes ?? {}),
184
214
  }}
185
215
  footer={footer}
186
- frameRef={assignFrameRef}
216
+ frameRef={setFrameRef}
187
217
  modalClassName={modalClassName}
188
218
  resolvedLabelledBy={resolvedLabelledBy}
189
219
  resizeHandles={maximized ? null : <ModalResizeHandles onResizeStart={handleResizeStart} />}
@@ -1,28 +1,28 @@
1
1
  import type { PointerEvent as ReactPointerEvent, ReactNode } from 'react';
2
2
 
3
- import { useWorkbenchHostPlatform } from '../workbench/WorkbenchPlatformContext';
3
+ import { useWorkbenchHostPlatform } from '../workbench/chrome/WorkbenchPlatformContext';
4
4
  import {
5
5
  shouldUseDarwinPlatformChrome,
6
6
  WorkbenchWindowChromeControls,
7
- } from '../workbench/WorkbenchWindowChromeControls';
7
+ } from '../workbench/shell/WorkbenchWindowChromeControls';
8
8
  import {
9
9
  resolveWorkbenchWindowChromeDataAttributes,
10
10
  type WorkbenchWindowChromeMode,
11
- } from '../workbench/workbenchPlatformChrome';
11
+ } from '../workbench/chrome/workbenchPlatformChrome';
12
12
 
13
13
  export interface ModalTitlebarProps {
14
- chrome?: WorkbenchWindowChromeMode | undefined;
15
- closeLabel?: string | undefined;
14
+ chrome?: WorkbenchWindowChromeMode;
15
+ closeLabel?: string;
16
16
  labelledBy: string;
17
17
  maximized: boolean;
18
- maximizeLabel?: string | undefined;
18
+ maximizeLabel?: string;
19
19
  onClose: () => void;
20
20
  onDoubleClick: () => void;
21
21
  onPointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;
22
22
  onToggleMaximized: () => void;
23
- restoreLabel?: string | undefined;
23
+ restoreLabel?: string;
24
24
  title: ReactNode;
25
- titleSuffix?: ReactNode | undefined;
25
+ titleSuffix?: ReactNode;
26
26
  }
27
27
 
28
28
  export function ModalTitlebar({
@@ -1,3 +1,4 @@
1
+ import { clampNumber } from '../utils/clamp';
1
2
  import type { ModalBounds, ModalPosition, ModalSize } from './modalTypes';
2
3
  import type { ModalContainerBounds } from './modalContainer';
3
4
  import { readWindowViewportBounds } from './modalContainer';
@@ -17,9 +18,9 @@ export function clampModalDragPosition(
17
18
  }
18
19
 
19
20
  const maxX = Math.max(0, viewport.width - size.width);
20
- const x = clamp(nextPosition.x, 0, maxX);
21
+ const x = clampNumber(nextPosition.x, 0, maxX);
21
22
  const maxY = size.height >= viewport.height ? 0 : Math.max(0, viewport.height - size.height);
22
- const y = clamp(nextPosition.y, 0, maxY);
23
+ const y = clampNumber(nextPosition.y, 0, maxY);
23
24
 
24
25
  return { x, y };
25
26
  }
@@ -30,7 +31,3 @@ export function clampModalBoundsPosition(
30
31
  ): ModalBounds {
31
32
  return { ...bounds, ...clampModalDragPosition(bounds, bounds, viewport) };
32
33
  }
33
-
34
- function clamp(value: number, min: number, max: number): number {
35
- return Math.min(Math.max(value, min), max);
36
- }