@workbench-kit/react 0.0.2-prototype.0.2.2 → 0.0.2-prototype.0.2.21
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.
- package/README.md +125 -0
- package/package.json +27 -20
- package/src/index.ts +60 -29
- package/src/jdw/JsonCodeEditorPane.tsx +7 -6
- package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +1 -3
- package/src/jdw/createBuiltinJdwRegistry.ts +1 -4
- package/src/jdw/cssRenderBackend.tsx +1 -4
- package/src/json-config/JsonConfigWorkbench.tsx +1 -1
- package/src/layout/index.ts +11 -0
- package/src/layout/sidebar/SideBarTree.tsx +301 -0
- package/src/layout/sidebar/SidebarActionIconBar.tsx +3 -3
- package/src/layout/sidebar/WorkbenchSidebarActions.tsx +1 -1
- package/src/layout/sidebar/index.ts +13 -0
- package/src/modal/Modal.tsx +55 -25
- package/src/modal/ModalTitlebar.tsx +8 -8
- package/src/modal/modalPosition.ts +3 -6
- package/src/modal/useModalFocusTrap.ts +142 -0
- package/src/modal/useModalWindowFrame.ts +2 -2
- package/src/overlay/ContextMenu.tsx +112 -5
- package/src/overlay/context-menu.css +4 -2
- package/src/overlay/measureAnchoredOverlayPanel.ts +4 -6
- package/src/overlay/usePointerPassthroughRegion.ts +2 -0
- package/src/primitives/absolute-box/AbsoluteBox.tsx +4 -4
- package/src/primitives/button/Button.tsx +4 -4
- package/src/primitives/catalog-browse-card/CatalogBrowseCard.tsx +6 -6
- package/src/primitives/catalog-browse-pane/CatalogBrowsePane.tsx +15 -15
- package/src/primitives/catalog-filter-overlay/CatalogFilterOverlay.tsx +2 -2
- package/src/primitives/checkbox/Checkbox.tsx +1 -1
- package/src/primitives/chip/Chip.tsx +2 -2
- package/src/primitives/codicon/Codicon.tsx +1 -1
- package/src/primitives/file-icon/FileIcon.tsx +1 -1
- package/src/primitives/icon-button/IconButton.tsx +1 -1
- package/src/primitives/index.ts +5 -0
- package/src/primitives/library-detail-layout/LibraryDetailLayout.tsx +123 -9
- package/src/primitives/library-detail-layout/index.ts +5 -0
- package/src/primitives/library-detail-layout/library-detail-layout.css +57 -0
- package/src/primitives/library-detail-layout/resolve-hero-cover-media.ts +80 -0
- package/src/primitives/library-facet-filter-panel/LibraryFacetFilterPanel.tsx +12 -12
- package/src/primitives/library-facet-filter-strip/LibraryFacetFilterStrip.tsx +4 -4
- package/src/primitives/number-input/NumberInput.tsx +5 -5
- package/src/primitives/record-media-hero/RecordMediaHero.tsx +10 -6
- package/src/primitives/scroll-area/ScrollArea.tsx +5 -5
- package/src/primitives/scroll-area-infinite-load/useScrollAreaInfiniteLoad.ts +5 -5
- package/src/primitives/searchable-multi-select/SearchableMultiSelect.tsx +7 -7
- package/src/primitives/select/Select.tsx +1 -13
- package/src/primitives/text-area/TextArea.tsx +1 -1
- package/src/primitives/text-input/TextInput.tsx +1 -1
- package/src/primitives/workbench-media-slot/WorkbenchMediaPlaceholder.tsx +2 -2
- package/src/primitives/workbench-media-slot/WorkbenchMediaSlot.tsx +16 -10
- package/src/primitives/workbench-thumbnail/WorkbenchThumbnail.tsx +4 -4
- package/src/styles.css +1 -2
- package/src/test-utils/workbenchMonacoMock.tsx +20 -0
- package/src/utils/clamp.ts +4 -0
- package/src/utils/normalizeKeyToken.ts +13 -0
- package/src/utils/readNumber.ts +4 -0
- package/src/widget-tree/WidgetTreeCanvasPreview.tsx +1 -4
- package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +2 -2
- package/src/workbench/chat/ChatCommandProposalCard.tsx +4 -4
- package/src/workbench/chat/ChatConversationBar.tsx +3 -3
- package/src/workbench/chat/ChatHistoryMenu.tsx +1 -1
- package/src/workbench/chat/ChatMessageCollapsible.tsx +4 -0
- package/src/workbench/chat/ChatMessageItem.tsx +68 -10
- package/src/workbench/chat/ChatMessageList.tsx +1 -1
- package/src/workbench/chat/ChatPanel.tsx +32 -23
- package/src/workbench/chat/ChatPhasedRunProgress.tsx +86 -13
- package/src/workbench/chat/ChatRenameDialog.tsx +1 -1
- package/src/workbench/chat/chat-conversation.css +2 -2
- package/src/workbench/chat/chat-ui.css +8 -0
- package/src/workbench/chat/chatConversation.ts +2 -2
- package/src/workbench/chat/chatMessageMeta.tsx +4 -4
- package/src/workbench/chat/index.ts +1 -0
- package/src/workbench/chat/types.ts +7 -7
- package/src/workbench/chat/useCancelRuntimeOnUnmount.ts +4 -4
- package/src/workbench/chat/useChatPrependPagination.ts +10 -10
- package/src/workbench/chat/useSlashCommandSuggest.ts +1 -1
- package/src/workbench/{WorkbenchModalPortal.tsx → chrome/WorkbenchModalPortal.tsx} +1 -1
- package/src/workbench/chrome/workbench-chrome.css +4 -0
- package/src/workbench/{CommandGroupShell.tsx → commands/CommandGroupShell.tsx} +2 -2
- package/src/workbench/{CommandList.tsx → commands/CommandList.tsx} +5 -5
- package/src/workbench/{CommandPalette.tsx → commands/CommandPalette.tsx} +16 -42
- package/src/workbench/{ConfirmationFlow.tsx → commands/ConfirmationFlow.tsx} +5 -5
- package/src/workbench/{ShortcutCommandBridge.tsx → commands/ShortcutCommandBridge.tsx} +1 -13
- package/src/workbench/commands/WorkbenchQuickOpen.tsx +462 -0
- package/src/workbench/{command-model.ts → commands/command-model.ts} +5 -1
- package/src/workbench/commands/command-ui.ts +3 -0
- package/src/workbench/{commands.ts → commands/commands.ts} +1 -1
- package/src/workbench/commands/createWorkspaceFilesQuickOpenProvider.ts +86 -0
- package/src/workbench/commands/quick-open-model.ts +66 -0
- package/src/workbench/{WorkbenchEditorTabs.tsx → editor/WorkbenchEditorTabs.tsx} +1 -1
- package/src/workbench/{editor-tabs-dnd.ts → editor/editor-tabs-dnd.ts} +1 -1
- package/src/workbench/{editorTabContextMenu.ts → editor/editorTabContextMenu.ts} +2 -2
- package/src/workbench/{useEditorTabsStripDnd.ts → editor/useEditorTabsStripDnd.ts} +1 -1
- package/src/workbench/{useWorkbenchEditorTabContextMenu.tsx → editor/useWorkbenchEditorTabContextMenu.tsx} +1 -1
- package/src/workbench/editor-tabs/index.ts +9 -9
- package/src/workbench/index.ts +110 -74
- package/src/workbench/management/ExtensionManagementPanel.tsx +32 -36
- package/src/workbench/management/ExtensionManagementSidebar.tsx +19 -35
- package/src/workbench/management/IntegrationSettingsSurface.tsx +2 -5
- package/src/workbench/management/IntegrationsShell.tsx +1 -1
- package/src/workbench/management/KeybindingCaptureField.tsx +1 -9
- package/src/workbench/management/WorkbenchDialogFrame.tsx +2 -2
- package/src/workbench/management/WorkbenchNotice.tsx +2 -5
- package/src/workbench/management/createWorkbenchNotify.tsx +88 -0
- package/src/workbench/management/extension-install-approval.ts +72 -0
- package/src/workbench/management/extension-management-filters.ts +38 -0
- package/src/workbench/management/index.ts +14 -0
- package/src/workbench/management/management.css +28 -0
- package/src/workbench/management/types.ts +14 -1
- package/src/workbench/management/useWorkbenchNotify.ts +10 -0
- package/src/workbench/{MarkdownPreview.tsx → markdown/MarkdownPreview.tsx} +22 -7
- package/src/workbench/markdown/markdownRemarkPlugins.ts +12 -0
- package/src/workbench/markdown/sanitizeMarkdownHref.ts +29 -0
- package/src/workbench/settings/StructuredDataForm.tsx +1 -4
- package/src/workbench/settings/structuredDataFormModel.ts +2 -1
- package/src/workbench/{ActivityBar.tsx → shell/ActivityBar.tsx} +8 -8
- package/src/workbench/{ArtifactShell.tsx → shell/ArtifactShell.tsx} +25 -25
- package/src/workbench/{SplitView.tsx → shell/SplitView.tsx} +3 -3
- package/src/workbench/{StatusBar.tsx → shell/StatusBar.tsx} +8 -8
- package/src/workbench/{StructuredArtifactEditor.tsx → shell/StructuredArtifactEditor.tsx} +9 -9
- package/src/workbench/{Timeline.tsx → shell/Timeline.tsx} +77 -20
- package/src/workbench/{WorkbenchCanvasShell.defaults.ts → shell/WorkbenchCanvasShell.defaults.ts} +1 -1
- package/src/workbench/{WorkbenchCanvasShell.model.ts → shell/WorkbenchCanvasShell.model.ts} +5 -5
- package/src/workbench/{WorkbenchCanvasShell.tsx → shell/WorkbenchCanvasShell.tsx} +6 -6
- package/src/workbench/{WorkbenchDesktopTitleBar.tsx → shell/WorkbenchDesktopTitleBar.tsx} +15 -15
- package/src/workbench/{WorkbenchShell.tsx → shell/WorkbenchShell.tsx} +45 -6
- package/src/workbench/{WorkbenchShellTitleBarLayoutControls.tsx → shell/WorkbenchShellTitleBarLayoutControls.tsx} +16 -19
- package/src/workbench/{WorkbenchStandaloneShell.tsx → shell/WorkbenchStandaloneShell.tsx} +4 -4
- package/src/workbench/{WorkbenchViewEditor.tsx → shell/WorkbenchViewEditor.tsx} +7 -7
- package/src/workbench/{WorkbenchViewSidebar.tsx → shell/WorkbenchViewSidebar.tsx} +7 -7
- package/src/workbench/{WorkbenchWindowChromeControls.tsx → shell/WorkbenchWindowChromeControls.tsx} +18 -15
- package/src/workbench/shell/activity-bar.css +47 -0
- package/src/workbench/{shell.ts → shell/shell.ts} +18 -12
- package/src/workbench/{shellViewModel.ts → shell/shellViewModel.ts} +3 -3
- package/src/workbench/{standalone.ts → shell/standalone.ts} +14 -2
- package/src/workbench/{useDeferredWorkbenchMount.ts → shell/useDeferredWorkbenchMount.ts} +3 -3
- package/src/workbench/{useWorkbenchAppearanceDocumentSync.tsx → shell/useWorkbenchAppearanceDocumentSync.tsx} +1 -1
- package/src/workbench/{useWorkbenchSidebarActionBarDnd.ts → shell/useWorkbenchSidebarActionBarDnd.ts} +1 -1
- package/src/workbench/{useWorkbenchSidebarViewPlacementDropZone.ts → shell/useWorkbenchSidebarViewPlacementDropZone.ts} +2 -2
- package/src/workbench/{workbenchViewRouteState.ts → shell/workbenchViewRouteState.ts} +4 -4
- package/src/workbench/{WorkbenchThemeProvider.tsx → theme/WorkbenchThemeProvider.tsx} +4 -4
- package/src/workbench/{theme.ts → theme/theme.ts} +1 -1
- package/src/workbench/{themePresets.ts → theme/themePresets.ts} +3 -2
- package/src/workbench/views/view-hosts.css +3 -3
- package/src/workbench/workspace/WorkspaceEditorPanel.tsx +1 -1
- package/src/workbench/workspace/WorkspaceExplorer.tsx +104 -3
- package/src/workbench/workspace/workspaceExplorerKeyboard.ts +109 -0
- package/src/workbench/workspace/workspaceJsonDiagnostics.ts +6 -5
- package/src/workbench/jdw/jdw-editors.css +0 -4
- package/src/workbench/jdw/jdw.css +0 -2
- package/src/workbench/markdownRemarkPlugins.ts +0 -4
- package/src/workbench/workbench-chrome.css +0 -4
- /package/src/workbench/{WorkbenchPlatformContext.tsx → chrome/WorkbenchPlatformContext.tsx} +0 -0
- /package/src/workbench/{workbenchModalViewState.ts → chrome/workbenchModalViewState.ts} +0 -0
- /package/src/workbench/{workbenchOverlaysContext.tsx → chrome/workbenchOverlaysContext.tsx} +0 -0
- /package/src/workbench/{workbenchPlatformChrome.ts → chrome/workbenchPlatformChrome.ts} +0 -0
- /package/src/workbench/{command-execution-policy.ts → commands/command-execution-policy.ts} +0 -0
- /package/src/workbench/{keyboard.ts → commands/keyboard.ts} +0 -0
- /package/src/workbench/{workbenchContextMenu.ts → commands/workbenchContextMenu.ts} +0 -0
- /package/src/workbench/{activityBarOrder.ts → shell/activityBarOrder.ts} +0 -0
- /package/src/workbench/{appearanceLabels.ts → shell/appearanceLabels.ts} +0 -0
- /package/src/workbench/{hardReset.ts → shell/hardReset.ts} +0 -0
- /package/src/workbench/{shellPresets.ts → shell/shellPresets.ts} +0 -0
- /package/src/workbench/{shellState.ts → shell/shellState.ts} +0 -0
- /package/src/workbench/{sidebarViewPlacementDnd.ts → shell/sidebarViewPlacementDnd.ts} +0 -0
- /package/src/workbench/{status.ts → shell/status.ts} +0 -0
- /package/src/workbench/{useWorkbenchSidebarLayout.tsx → shell/useWorkbenchSidebarLayout.tsx} +0 -0
- /package/src/workbench/{useWorkbenchStandaloneShellStateSync.tsx → shell/useWorkbenchStandaloneShellStateSync.tsx} +0 -0
- /package/src/workbench/{workbenchSidebarLayoutContext.ts → shell/workbenchSidebarLayoutContext.ts} +0 -0
- /package/src/workbench/{workbenchStandaloneShellReactContext.tsx → shell/workbenchStandaloneShellReactContext.tsx} +0 -0
|
@@ -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
|
|
|
@@ -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';
|
package/src/modal/Modal.tsx
CHANGED
|
@@ -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
|
|
27
|
+
chrome?: WorkbenchWindowChromeMode;
|
|
24
28
|
title: ReactNode;
|
|
25
|
-
titleSuffix?: ReactNode
|
|
29
|
+
titleSuffix?: ReactNode;
|
|
26
30
|
children: ReactNode;
|
|
27
|
-
footer?: ReactNode
|
|
28
|
-
className?: string
|
|
29
|
-
bodyClassName?: string
|
|
30
|
-
bodyLayout?: ModalBodyLayout
|
|
31
|
-
bodyPadding?: ModalBodyPadding
|
|
32
|
-
bodyScroll?: 'auto' | 'hidden'
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
43
|
-
|
|
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
|
|
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
|
|
63
|
+
footer?: ReactNode;
|
|
54
64
|
frameRef: (node: HTMLDivElement | HTMLFormElement | null) => void;
|
|
55
65
|
modalClassName: string;
|
|
56
66
|
resolvedLabelledBy: string;
|
|
57
|
-
resizeHandles?: ReactNode
|
|
58
|
-
style?: CSSProperties
|
|
67
|
+
resizeHandles?: ReactNode;
|
|
68
|
+
style?: CSSProperties;
|
|
59
69
|
titlebar: ReactNode;
|
|
60
|
-
onSubmit?: FormEventHandler<HTMLFormElement
|
|
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={
|
|
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
|
|
15
|
-
closeLabel?: string
|
|
14
|
+
chrome?: WorkbenchWindowChromeMode;
|
|
15
|
+
closeLabel?: string;
|
|
16
16
|
labelledBy: string;
|
|
17
17
|
maximized: boolean;
|
|
18
|
-
maximizeLabel?: string
|
|
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
|
|
23
|
+
restoreLabel?: string;
|
|
24
24
|
title: ReactNode;
|
|
25
|
-
titleSuffix?: ReactNode
|
|
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 =
|
|
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 =
|
|
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
|
-
}
|