@workbench-kit/react 0.0.2-prototype.0.2.32 → 0.0.2-prototype.0.2.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-kit/react",
3
- "version": "0.0.2-prototype.0.2.32",
3
+ "version": "0.0.2-prototype.0.2.34",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -73,15 +73,15 @@
73
73
  "@vscode/codicons": "^0.0.45",
74
74
  "remark-gfm": "^4.0.1",
75
75
  "rehype-sanitize": "^6.0.0",
76
- "@workbench-kit/contracts": "0.0.2-prototype.0.2.32",
77
- "@workbench-kit/adapters": "0.0.2-prototype.0.2.32",
78
- "@workbench-kit/monaco": "0.0.2-prototype.0.2.32",
79
- "@workbench-kit/tokens": "0.0.2-prototype.0.2.32",
80
- "@workbench-kit/platform": "0.0.2-prototype.0.2.32",
81
- "@workbench-kit/runtime": "0.0.2-prototype.0.2.32",
82
- "@workbench-kit/workspace": "0.0.2-prototype.0.2.32",
83
- "@workbench-kit/services": "0.0.2-prototype.0.2.32",
84
- "@workbench-kit/jdw": "0.0.2-prototype.0.2.32"
76
+ "@workbench-kit/jdw": "0.0.2-prototype.0.2.34",
77
+ "@workbench-kit/adapters": "0.0.2-prototype.0.2.34",
78
+ "@workbench-kit/contracts": "0.0.2-prototype.0.2.34",
79
+ "@workbench-kit/monaco": "0.0.2-prototype.0.2.34",
80
+ "@workbench-kit/platform": "0.0.2-prototype.0.2.34",
81
+ "@workbench-kit/tokens": "0.0.2-prototype.0.2.34",
82
+ "@workbench-kit/services": "0.0.2-prototype.0.2.34",
83
+ "@workbench-kit/workspace": "0.0.2-prototype.0.2.34",
84
+ "@workbench-kit/runtime": "0.0.2-prototype.0.2.34"
85
85
  },
86
86
  "peerDependencies": {
87
87
  "react": "^19.0.0",
package/src/index.ts CHANGED
@@ -19,6 +19,11 @@ export type {
19
19
  WorkbenchIconResolver,
20
20
  } from './icons';
21
21
  export { commandMenuItemsToContextMenuItems } from './workbench/commands/commands';
22
+ export type {
23
+ ExtensionManagementEntry,
24
+ ExtensionManagementPanelProps,
25
+ ExtensionManagementTransition,
26
+ } from './workbench/management';
22
27
  export {
23
28
  WorkbenchArtifactModeControls,
24
29
  WorkbenchArtifactPreview,
@@ -213,15 +218,20 @@ export type {
213
218
  export {
214
219
  createPointerPassthroughController,
215
220
  isPointerOverHitRegion,
221
+ resolvePointerHitTarget,
216
222
  } from './overlay/pointerPassthroughRegion';
217
223
  export type {
224
+ PointerHitTestDocument,
218
225
  PointerOverHitRegionOptions,
219
226
  PointerPassthroughController,
220
227
  PointerPassthroughControllerOptions,
221
228
  PointerPassthroughPort,
222
229
  } from './overlay/pointerPassthroughRegion';
223
230
  export { usePointerPassthroughRegion } from './overlay/usePointerPassthroughRegion';
224
- export type { UsePointerPassthroughRegionOptions } from './overlay/usePointerPassthroughRegion';
231
+ export type {
232
+ PointerHitTargetResolver,
233
+ UsePointerPassthroughRegionOptions,
234
+ } from './overlay/usePointerPassthroughRegion';
225
235
  export {
226
236
  getWorkbenchStatusDescriptor,
227
237
  getWorkbenchStatusLabel,
@@ -743,6 +753,7 @@ export {
743
753
  } from './workbench/editor/editorTabContextMenu';
744
754
  export type {
745
755
  CreateWorkbenchStandaloneEditorTabCommandContextInput,
756
+ CreateWorkbenchStandaloneEditorTabContextMenuItemsInput,
746
757
  WorkbenchStandaloneEditorTabLike,
747
758
  } from './workbench/editor/editorTabContextMenu';
748
759
  export { useWorkbenchEditorTabContextMenu } from './workbench/editor/useWorkbenchEditorTabContextMenu';
@@ -1,5 +1,5 @@
1
1
  import './context-menu.css';
2
- import { useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react';
2
+ import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react';
3
3
  import { Button } from '../primitives/button';
4
4
  import { cxCodicon } from '../utils/codicon';
5
5
  import { cx } from '../utils/cx';
@@ -13,6 +13,8 @@ export interface ContextMenuProps {
13
13
  ariaLabel?: string | undefined;
14
14
  className?: string | undefined;
15
15
  items: ContextMenuItem[];
16
+ /** Explicit focus target restored only when Escape dismisses the menu. */
17
+ returnFocusTarget?: HTMLElement | null | undefined;
16
18
  x: number;
17
19
  y: number;
18
20
  onClose: () => void;
@@ -77,18 +79,33 @@ export function ContextMenu({
77
79
  ariaLabel = 'Context menu',
78
80
  className,
79
81
  items,
82
+ returnFocusTarget,
80
83
  x,
81
84
  y,
82
85
  onClose,
83
86
  }: ContextMenuProps) {
84
87
  const ref = useRef<HTMLDivElement>(null);
88
+ const [fallbackReturnFocusTarget] = useState<HTMLElement | null>(() => {
89
+ if (typeof document === 'undefined') {
90
+ return null;
91
+ }
92
+ return document.activeElement instanceof HTMLElement ? document.activeElement : null;
93
+ });
85
94
  const position = useClampedFixedOverlayPosition(ref, { x, y }, items.length);
86
95
  const hasIcons = menuHasIcons(items);
87
96
  const hasShortcuts = menuHasShortcuts(items);
88
97
  const enabledIndexes = useMemo(() => getEnabledItemIndexes(items), [items]);
89
98
  const [highlightedIndex, setHighlightedIndex] = useState(() => enabledIndexes[0] ?? -1);
90
99
 
91
- useFixedOverlayDismiss({ containerRef: ref, onClose });
100
+ const restoreFocusOnEscape = useCallback(() => {
101
+ const target = returnFocusTarget ?? fallbackReturnFocusTarget;
102
+ if (target?.isConnected) {
103
+ target.focus();
104
+ }
105
+ return false;
106
+ }, [fallbackReturnFocusTarget, returnFocusTarget]);
107
+
108
+ useFixedOverlayDismiss({ containerRef: ref, onClose, onEscape: restoreFocusOnEscape });
92
109
 
93
110
  useEffect(() => {
94
111
  const handleContextMenu = (event: MouseEvent) => {
@@ -20,12 +20,17 @@ export type {
20
20
  export {
21
21
  createPointerPassthroughController,
22
22
  isPointerOverHitRegion,
23
+ resolvePointerHitTarget,
23
24
  } from './pointerPassthroughRegion';
24
25
  export type {
26
+ PointerHitTestDocument,
25
27
  PointerOverHitRegionOptions,
26
28
  PointerPassthroughController,
27
29
  PointerPassthroughControllerOptions,
28
30
  PointerPassthroughPort,
29
31
  } from './pointerPassthroughRegion';
30
32
  export { usePointerPassthroughRegion } from './usePointerPassthroughRegion';
31
- export type { UsePointerPassthroughRegionOptions } from './usePointerPassthroughRegion';
33
+ export type {
34
+ PointerHitTargetResolver,
35
+ UsePointerPassthroughRegionOptions,
36
+ } from './usePointerPassthroughRegion';
@@ -2,6 +2,22 @@ export interface PointerPassthroughPort {
2
2
  setPointerPassthrough(enabled: boolean): void | Promise<void>;
3
3
  }
4
4
 
5
+ export interface PointerHitTestDocument {
6
+ elementFromPoint(x: number, y: number): Element | null;
7
+ }
8
+
9
+ /** Resolve the painted element at finite viewport coordinates. */
10
+ export function resolvePointerHitTarget(
11
+ clientX: number,
12
+ clientY: number,
13
+ documentSurface: PointerHitTestDocument = document,
14
+ ): Element | null {
15
+ if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
16
+ return null;
17
+ }
18
+ return documentSurface.elementFromPoint(clientX, clientY);
19
+ }
20
+
5
21
  export interface PointerOverHitRegionOptions {
6
22
  readonly hitSelectors: readonly string[];
7
23
  readonly controlSelectors?: readonly string[];
@@ -2,9 +2,12 @@ import { useEffect, useRef, type RefObject } from 'react';
2
2
 
3
3
  import {
4
4
  createPointerPassthroughController,
5
+ resolvePointerHitTarget,
5
6
  type PointerPassthroughPort,
6
7
  } from './pointerPassthroughRegion';
7
8
 
9
+ export type PointerHitTargetResolver = (clientX: number, clientY: number) => EventTarget | null;
10
+
8
11
  export interface UsePointerPassthroughRegionOptions {
9
12
  readonly enabled: boolean;
10
13
  readonly port: PointerPassthroughPort;
@@ -14,11 +17,15 @@ export interface UsePointerPassthroughRegionOptions {
14
17
  readonly controlSelectors?: readonly string[];
15
18
  /** Limit hit-testing to a subtree; defaults to the document. */
16
19
  readonly rootRef?: RefObject<HTMLElement | null>;
20
+ /** Override painted-element resolution; defaults to `document.elementFromPoint`. */
21
+ readonly resolveHitTarget?: PointerHitTargetResolver;
17
22
  }
18
23
 
19
24
  /**
20
25
  * Toggles host pointer passthrough from renderer hit-testing.
21
- * Uses `requestAnimationFrame` coalescing for pointermove; no product selectors in kit.
26
+ * Uses `requestAnimationFrame` coalescing and painted-element hit-testing; no
27
+ * product selectors in kit. Electron can forward move events while ignoring
28
+ * mouse input, in which case `event.target` is not necessarily under the cursor.
22
29
  *
23
30
  * Host responsibilities:
24
31
  * - Provide selector lists
@@ -26,7 +33,7 @@ export interface UsePointerPassthroughRegionOptions {
26
33
  * - Pair with platform residency applicator for the main-process half
27
34
  */
28
35
  export function usePointerPassthroughRegion(options: UsePointerPassthroughRegionOptions): void {
29
- const { enabled, port, hitSelectors, controlSelectors, rootRef } = options;
36
+ const { enabled, port, hitSelectors, controlSelectors, rootRef, resolveHitTarget } = options;
30
37
 
31
38
  const portRef = useRef(port);
32
39
  portRef.current = port;
@@ -34,6 +41,8 @@ export function usePointerPassthroughRegion(options: UsePointerPassthroughRegion
34
41
  hitSelectorsRef.current = hitSelectors;
35
42
  const controlSelectorsRef = useRef(controlSelectors);
36
43
  controlSelectorsRef.current = controlSelectors;
44
+ const resolveHitTargetRef = useRef(resolveHitTarget ?? resolvePointerHitTarget);
45
+ resolveHitTargetRef.current = resolveHitTarget ?? resolvePointerHitTarget;
37
46
 
38
47
  useEffect(() => {
39
48
  const controller = createPointerPassthroughController({
@@ -58,25 +67,45 @@ export function usePointerPassthroughRegion(options: UsePointerPassthroughRegion
58
67
  }
59
68
 
60
69
  let frame = 0;
61
- let pendingTarget: EventTarget | null = null;
70
+ let pendingX = 0;
71
+ let pendingY = 0;
62
72
 
63
73
  const flush = () => {
64
74
  frame = 0;
65
- controller.handlePointerTarget(pendingTarget);
75
+ // Re-assert on every frame. Host state may also change outside this hook;
76
+ // the main-process port should make same-value updates a no-op.
77
+ controller.reset();
78
+ controller.handlePointerTarget(resolveHitTargetRef.current(pendingX, pendingY));
66
79
  };
67
80
 
68
- const onPointerMove = (event: PointerEvent) => {
69
- pendingTarget = event.target;
81
+ const onPointerMove = (event: PointerEvent | MouseEvent) => {
82
+ pendingX = event.clientX;
83
+ pendingY = event.clientY;
70
84
  if (frame !== 0) {
71
85
  return;
72
86
  }
73
87
  frame = window.requestAnimationFrame(flush);
74
88
  };
75
89
 
90
+ const onMouseLeave = () => {
91
+ if (frame !== 0) {
92
+ window.cancelAnimationFrame(frame);
93
+ frame = 0;
94
+ }
95
+ controller.reset();
96
+ controller.handlePointerTarget(null);
97
+ };
98
+
76
99
  window.addEventListener('pointermove', onPointerMove, { passive: true });
100
+ // Some Electron builds forward mousemove rather than pointermove while the
101
+ // native window ignores input.
102
+ window.addEventListener('mousemove', onPointerMove, { passive: true });
103
+ document.documentElement.addEventListener('mouseleave', onMouseLeave);
77
104
 
78
105
  return () => {
79
106
  window.removeEventListener('pointermove', onPointerMove);
107
+ window.removeEventListener('mousemove', onPointerMove);
108
+ document.documentElement.removeEventListener('mouseleave', onMouseLeave);
80
109
  if (frame !== 0) {
81
110
  window.cancelAnimationFrame(frame);
82
111
  }
@@ -23,6 +23,7 @@ export const WORKBENCH_EDITOR_SPLIT_RIGHT_COMMAND_ID = 'editor.splitRight';
23
23
  export const WORKBENCH_EDITOR_SPLIT_DOWN_COMMAND_ID = 'editor.splitDown';
24
24
  export const WORKBENCH_EDITOR_CLOSE_COMMAND_ID = 'editor.close';
25
25
  export const WORKBENCH_EDITOR_CLOSE_OTHERS_COMMAND_ID = 'editor.closeOthers';
26
+ export const WORKBENCH_EDITOR_CLOSE_TO_RIGHT_COMMAND_ID = 'editor.closeToRight';
26
27
  export const WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID = 'editor.closeAll';
27
28
  export const WORKBENCH_EDITOR_DELETE_COMMAND_ID = 'editor.delete';
28
29
  export const WORKBENCH_WORKSPACE_NEW_FILE_COMMAND_ID = 'workspace.newFile';
@@ -113,6 +114,8 @@ export interface WorkbenchShellCommandPresetOptions<TActivityId extends string =
113
114
  export interface WorkbenchEditorCommandContext {
114
115
  canCloseAll: boolean;
115
116
  canCloseOthers: boolean;
117
+ /** Optional so existing public command-context object literals remain source-compatible. */
118
+ canCloseToRight?: boolean | undefined;
116
119
  canClosePath: boolean;
117
120
  canCopyPath: boolean;
118
121
  canDeletePath: boolean;
@@ -123,6 +126,8 @@ export interface WorkbenchEditorCommandContext {
123
126
  canTogglePinned: boolean;
124
127
  closeAll: () => void;
125
128
  closeOthers: () => void;
129
+ /** Presence advertises support and makes Close to the right visible. */
130
+ closeToRight?: (() => void) | undefined;
126
131
  closePath: () => void;
127
132
  copyPath: () => void;
128
133
  deletePath: () => void;
@@ -342,6 +347,14 @@ export function createWorkbenchEditorCommands({
342
347
  label: 'Close others',
343
348
  run: ({ closeOthers }) => closeOthers(),
344
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
+ },
345
358
  {
346
359
  id: WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID,
347
360
  icon: 'codicon-close-all',
@@ -407,6 +420,9 @@ export function createWorkbenchStandaloneEditorTabMenuEntries(): CommandMenuEntr
407
420
  commandMenuEntry<WorkbenchEditorCommandContext>(WORKBENCH_EDITOR_CLOSE_OTHERS_COMMAND_ID, {
408
421
  surfaces: [WORKBENCH_COMMAND_SURFACE_EDITOR],
409
422
  }),
423
+ commandMenuEntry<WorkbenchEditorCommandContext>(WORKBENCH_EDITOR_CLOSE_TO_RIGHT_COMMAND_ID, {
424
+ surfaces: [WORKBENCH_COMMAND_SURFACE_EDITOR],
425
+ }),
410
426
  commandMenuEntry<WorkbenchEditorCommandContext>(WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID, {
411
427
  surfaces: [WORKBENCH_COMMAND_SURFACE_EDITOR],
412
428
  }),
@@ -1,7 +1,11 @@
1
1
  import { EditorTabs, type EditorTabsProps } from '../../primitives/workbench-editor';
2
+ import type { ContextMenuItem } from '../../overlay/ContextMenu';
2
3
  import { useWorkbenchEditorTabContextMenu } from './useWorkbenchEditorTabContextMenu';
3
4
 
4
5
  export interface WorkbenchEditorTabsProps extends EditorTabsProps {
6
+ /** Additional host actions appended after the built-in close group. */
7
+ readonly getExtraTabContextMenuItems?:
8
+ ((tabId: string) => readonly ContextMenuItem[] | undefined) | undefined;
5
9
  /**
6
10
  * Optional bulk close. Defaults to calling `onClose` for every closable tab.
7
11
  */
@@ -10,26 +14,32 @@ export interface WorkbenchEditorTabsProps extends EditorTabsProps {
10
14
  * Optional close-others. Defaults to calling `onClose` for every other closable tab.
11
15
  */
12
16
  readonly onCloseOthers?: ((tabId: string) => void) | undefined;
17
+ /** Optional close-to-right override. Defaults to closing each later closable tab. */
18
+ readonly onCloseToRight?: ((tabId: string) => void) | undefined;
13
19
  }
14
20
 
15
21
  /**
16
- * Editor tab strip with a built-in Close / Close others / Close all context menu.
22
+ * Editor tab strip with a built-in Close / Close others / Close to the right / Close all menu.
17
23
  * Intended for `WorkbenchStandaloneShell` secondary areas that own tab state in the host.
18
- * Tabs with `closable: false` keep Close disabled and are skipped by Close others / Close all.
24
+ * Tabs with `closable: false` keep Close disabled and are skipped by every bulk close action.
19
25
  */
20
26
  export function WorkbenchEditorTabs({
27
+ getExtraTabContextMenuItems,
21
28
  onClose,
22
29
  onCloseAll,
23
30
  onCloseOthers,
31
+ onCloseToRight,
24
32
  onSelect,
25
33
  onTabContextMenu,
26
34
  tabs,
27
35
  ...props
28
36
  }: WorkbenchEditorTabsProps) {
29
37
  const tabContextMenu = useWorkbenchEditorTabContextMenu({
38
+ getExtraTabContextMenuItems,
30
39
  onClose: onClose ?? (() => undefined),
31
40
  onCloseAll,
32
41
  onCloseOthers,
42
+ onCloseToRight,
33
43
  onSelectTab: onSelect,
34
44
  tabs,
35
45
  });
@@ -29,25 +29,41 @@ export interface CreateWorkbenchStandaloneEditorTabCommandContextInput {
29
29
  readonly onClose: (tabId: string) => void;
30
30
  readonly onCloseAll?: (() => void) | undefined;
31
31
  readonly onCloseOthers?: ((tabId: string) => void) | undefined;
32
+ readonly onCloseToRight?: ((tabId: string) => void) | undefined;
32
33
  readonly tabId: string;
33
34
  readonly tabs: readonly WorkbenchStandaloneEditorTabLike[];
34
35
  }
35
36
 
37
+ export interface CreateWorkbenchStandaloneEditorTabContextMenuItemsInput extends CreateWorkbenchStandaloneEditorTabCommandContextInput {
38
+ readonly getExtraTabContextMenuItems?:
39
+ ((tabId: string) => readonly ContextMenuItem[] | undefined) | undefined;
40
+ }
41
+
36
42
  export function createWorkbenchStandaloneEditorTabCommandContext({
37
43
  onClose,
38
44
  onCloseAll,
39
45
  onCloseOthers,
46
+ onCloseToRight,
40
47
  tabId,
41
48
  tabs,
42
49
  }: CreateWorkbenchStandaloneEditorTabCommandContextInput): WorkbenchEditorCommandContext {
43
50
  const targetTab = tabs.find((tab) => tab.id === tabId);
51
+ const targetIndex = tabs.findIndex((tab) => tab.id === tabId);
44
52
  const closableTabIds = tabs.filter(isWorkbenchEditorTabClosable).map((tab) => tab.id);
45
53
  const otherClosableTabIds = closableTabIds.filter((id) => id !== tabId);
54
+ const rightClosableTabIds =
55
+ targetIndex < 0
56
+ ? []
57
+ : tabs
58
+ .slice(targetIndex + 1)
59
+ .filter(isWorkbenchEditorTabClosable)
60
+ .map((tab) => tab.id);
46
61
  const canCloseTarget = Boolean(targetTab && isWorkbenchEditorTabClosable(targetTab));
47
62
 
48
63
  return {
49
64
  canCloseAll: closableTabIds.length > 0,
50
65
  canCloseOthers: otherClosableTabIds.length > 0,
66
+ canCloseToRight: rightClosableTabIds.length > 0,
51
67
  canClosePath: canCloseTarget,
52
68
  canCopyPath: false,
53
69
  canDeletePath: false,
@@ -74,6 +90,18 @@ export function createWorkbenchStandaloneEditorTabCommandContext({
74
90
  onClose(id);
75
91
  }
76
92
  },
93
+ closeToRight: () => {
94
+ if (rightClosableTabIds.length === 0) {
95
+ return;
96
+ }
97
+ if (onCloseToRight) {
98
+ onCloseToRight(tabId);
99
+ return;
100
+ }
101
+ for (const id of rightClosableTabIds) {
102
+ onClose(id);
103
+ }
104
+ },
77
105
  closePath: () => {
78
106
  if (canCloseTarget) {
79
107
  onClose(tabId);
@@ -95,11 +123,11 @@ export function createWorkbenchStandaloneEditorTabCommandContext({
95
123
  }
96
124
 
97
125
  export function createWorkbenchStandaloneEditorTabContextMenuItems(
98
- input: CreateWorkbenchStandaloneEditorTabCommandContextInput,
126
+ input: CreateWorkbenchStandaloneEditorTabContextMenuItemsInput,
99
127
  ): ContextMenuItem[] {
100
128
  const context = createWorkbenchStandaloneEditorTabCommandContext(input);
101
129
 
102
- return commandMenuItemsToContextMenuItems(
130
+ const builtInItems = commandMenuItemsToContextMenuItems(
103
131
  resolveCommandMenuItems({
104
132
  context,
105
133
  entries: standaloneEditorTabMenuEntries,
@@ -108,4 +136,15 @@ export function createWorkbenchStandaloneEditorTabContextMenuItems(
108
136
  }),
109
137
  (commandId) => executeCommand(editorCommandRegistry, commandId, context),
110
138
  );
139
+ const extraItems = input.getExtraTabContextMenuItems?.(input.tabId) ?? [];
140
+
141
+ if (extraItems.length === 0) {
142
+ return builtInItems;
143
+ }
144
+
145
+ return [
146
+ ...builtInItems,
147
+ { id: 'standalone-tab-extra-separator', type: 'separator' },
148
+ ...extraItems,
149
+ ];
111
150
  }
@@ -1,15 +1,18 @@
1
1
  import { useCallback, useState, type MouseEvent as ReactMouseEvent, type ReactNode } from 'react';
2
2
 
3
- import { ContextMenu } from '../../overlay/ContextMenu';
3
+ import { ContextMenu, type ContextMenuItem } from '../../overlay/ContextMenu';
4
4
  import {
5
5
  createWorkbenchStandaloneEditorTabContextMenuItems,
6
6
  type WorkbenchStandaloneEditorTabLike,
7
7
  } from './editorTabContextMenu';
8
8
 
9
9
  export interface UseWorkbenchEditorTabContextMenuOptions {
10
+ readonly getExtraTabContextMenuItems?:
11
+ ((tabId: string) => readonly ContextMenuItem[] | undefined) | undefined;
10
12
  readonly onClose: (tabId: string) => void;
11
13
  readonly onCloseAll?: (() => void) | undefined;
12
14
  readonly onCloseOthers?: ((tabId: string) => void) | undefined;
15
+ readonly onCloseToRight?: ((tabId: string) => void) | undefined;
13
16
  readonly onSelectTab?: ((tabId: string) => void) | undefined;
14
17
  readonly tabs: readonly WorkbenchStandaloneEditorTabLike[];
15
18
  }
@@ -20,13 +23,16 @@ export interface UseWorkbenchEditorTabContextMenuResult {
20
23
  }
21
24
 
22
25
  export function useWorkbenchEditorTabContextMenu({
26
+ getExtraTabContextMenuItems,
23
27
  onClose,
24
28
  onCloseAll,
25
29
  onCloseOthers,
30
+ onCloseToRight,
26
31
  onSelectTab,
27
32
  tabs,
28
33
  }: UseWorkbenchEditorTabContextMenuOptions): UseWorkbenchEditorTabContextMenuResult {
29
34
  const [menuState, setMenuState] = useState<{
35
+ returnFocusTarget: HTMLElement;
30
36
  tabId: string;
31
37
  x: number;
32
38
  y: number;
@@ -37,7 +43,12 @@ export function useWorkbenchEditorTabContextMenu({
37
43
  event.preventDefault();
38
44
  event.stopPropagation();
39
45
  onSelectTab?.(tabId);
40
- setMenuState({ tabId, x: event.clientX, y: event.clientY });
46
+ setMenuState({
47
+ returnFocusTarget: event.currentTarget,
48
+ tabId,
49
+ x: event.clientX,
50
+ y: event.clientY,
51
+ });
41
52
  },
42
53
  [onSelectTab],
43
54
  );
@@ -47,12 +58,15 @@ export function useWorkbenchEditorTabContextMenu({
47
58
  <ContextMenu
48
59
  ariaLabel="Editor tab menu"
49
60
  items={createWorkbenchStandaloneEditorTabContextMenuItems({
61
+ getExtraTabContextMenuItems,
50
62
  onClose,
51
63
  onCloseAll,
52
64
  onCloseOthers,
65
+ onCloseToRight,
53
66
  tabId: menuState.tabId,
54
67
  tabs,
55
68
  })}
69
+ returnFocusTarget={menuState.returnFocusTarget}
56
70
  x={menuState.x}
57
71
  y={menuState.y}
58
72
  onClose={() => setMenuState(null)}
@@ -6,6 +6,7 @@ export {
6
6
  } from '../editor/editorTabContextMenu';
7
7
  export type {
8
8
  CreateWorkbenchStandaloneEditorTabCommandContextInput,
9
+ CreateWorkbenchStandaloneEditorTabContextMenuItemsInput,
9
10
  WorkbenchStandaloneEditorTabLike,
10
11
  } from '../editor/editorTabContextMenu';
11
12
  export { useEditorTabsStripDnd } from '../editor/useEditorTabsStripDnd';
@@ -298,6 +298,7 @@ export {
298
298
  WORKBENCH_EDITOR_CLOSE_ALL_COMMAND_ID,
299
299
  WORKBENCH_EDITOR_CLOSE_COMMAND_ID,
300
300
  WORKBENCH_EDITOR_CLOSE_OTHERS_COMMAND_ID,
301
+ WORKBENCH_EDITOR_CLOSE_TO_RIGHT_COMMAND_ID,
301
302
  WORKBENCH_EDITOR_COPY_PATH_COMMAND_ID,
302
303
  WORKBENCH_EDITOR_DELETE_COMMAND_ID,
303
304
  WORKBENCH_EDITOR_DISCARD_CHANGES_COMMAND_ID,
@@ -449,6 +450,7 @@ export type {
449
450
  ExtensionManagementEntry,
450
451
  ExtensionManagementPanelProps,
451
452
  ExtensionManagementSidebarProps,
453
+ ExtensionManagementTransition,
452
454
  KeybindingManagementPanelProps,
453
455
  ManagementPanelFrameProps,
454
456
  ManagementPanelToolbarProps,
@@ -605,6 +607,7 @@ export {
605
607
  } from './editor/editorTabContextMenu';
606
608
  export type {
607
609
  CreateWorkbenchStandaloneEditorTabCommandContextInput,
610
+ CreateWorkbenchStandaloneEditorTabContextMenuItemsInput,
608
611
  WorkbenchStandaloneEditorTabLike,
609
612
  } from './editor/editorTabContextMenu';
610
613
  export { useWorkbenchEditorTabContextMenu } from './editor/useWorkbenchEditorTabContextMenu';
@@ -24,6 +24,7 @@ import type {
24
24
  ExtensionManagementEntry,
25
25
  ExtensionManagementFeatureSummary,
26
26
  ExtensionManagementPanelProps,
27
+ ExtensionManagementTransition,
27
28
  } from './types.js';
28
29
 
29
30
  const BROWSE_CATEGORIES = ['all', 'feature', 'editor', 'theme', 'language'] as const;
@@ -46,6 +47,7 @@ export function ExtensionManagementPanel({
46
47
  onInstall,
47
48
  onRememberInstallTrust,
48
49
  onToggleEnabled,
50
+ onUninstall,
49
51
  }: ExtensionManagementPanelProps) {
50
52
  const [activeTab, setActiveTab] = useState<'installed' | 'browse'>(
51
53
  EXTENSION_MANAGEMENT_DEFAULT_TAB,
@@ -93,6 +95,7 @@ export function ExtensionManagementPanel({
93
95
  summary={`${filteredInstalled.length} of ${installedEntries.length} visible`}
94
96
  onQueryChange={setInstalledQuery}
95
97
  onToggleEnabled={onToggleEnabled}
98
+ onUninstall={onUninstall}
96
99
  />
97
100
  ),
98
101
  },
@@ -122,7 +125,8 @@ export function ExtensionManagementPanel({
122
125
  onSelect={(tabId) => setActiveTab(tabId as 'installed' | 'browse')}
123
126
  />
124
127
  <ManagementPanelNotice>
125
- Installing or toggling extensions reloads the workbench to apply contributions.
128
+ Eligible unselected theme packs apply immediately. Other extension changes reload the
129
+ workbench.
126
130
  </ManagementPanelNotice>
127
131
  </ManagementPanelFrame>
128
132
  );
@@ -133,6 +137,7 @@ function InstalledExtensionsTab({
133
137
  entries,
134
138
  onQueryChange,
135
139
  onToggleEnabled,
140
+ onUninstall,
136
141
  query,
137
142
  summary,
138
143
  }: {
@@ -140,6 +145,7 @@ function InstalledExtensionsTab({
140
145
  entries: readonly ExtensionManagementEntry[];
141
146
  onQueryChange: (query: string) => void;
142
147
  onToggleEnabled?: ExtensionManagementPanelProps['onToggleEnabled'];
148
+ onUninstall?: ExtensionManagementPanelProps['onUninstall'];
143
149
  query: string;
144
150
  summary: string;
145
151
  }) {
@@ -160,15 +166,27 @@ function InstalledExtensionsTab({
160
166
  <li key={entry.id}>
161
167
  <ManagementCard
162
168
  actions={
163
- <Button
164
- compact
165
- disabled={!onToggleEnabled || entry.source === 'bundled'}
166
- type="button"
167
- variant={entry.enabled ? 'default' : 'primary'}
168
- onClick={() => onToggleEnabled?.(entry, !entry.enabled)}
169
- >
170
- {entry.enabled ? 'Disable' : 'Enable'}
171
- </Button>
169
+ <>
170
+ <Button
171
+ compact
172
+ disabled={!onToggleEnabled || entry.source === 'bundled'}
173
+ type="button"
174
+ variant={entry.enabled ? 'default' : 'primary'}
175
+ onClick={() => onToggleEnabled?.(entry, !entry.enabled)}
176
+ >
177
+ {entry.enabled ? 'Disable' : 'Enable'}
178
+ </Button>
179
+ {entry.canUninstall === true && onUninstall ? (
180
+ <Button
181
+ compact
182
+ type="button"
183
+ variant="danger"
184
+ onClick={() => onUninstall(entry)}
185
+ >
186
+ Uninstall
187
+ </Button>
188
+ ) : null}
189
+ </>
172
190
  }
173
191
  badges={
174
192
  <>
@@ -180,6 +198,14 @@ function InstalledExtensionsTab({
180
198
  {entry.enabled ? 'Enabled' : 'Disabled'}
181
199
  </Badge>
182
200
  )}
201
+ {entry.transition ? (
202
+ <Badge
203
+ title={entry.transition.message}
204
+ variant={extensionTransitionBadgeVariant(entry.transition.kind)}
205
+ >
206
+ {extensionTransitionLabel(entry.transition.kind)}
207
+ </Badge>
208
+ ) : null}
183
209
  </>
184
210
  }
185
211
  description={entry.description}
@@ -198,6 +224,22 @@ function InstalledExtensionsTab({
198
224
  );
199
225
  }
200
226
 
227
+ function extensionTransitionLabel(kind: ExtensionManagementTransition['kind']): string {
228
+ if (kind === 'reloadRequired') {
229
+ return 'Reload required';
230
+ }
231
+ return kind === 'failed' ? 'Failed' : 'Applied';
232
+ }
233
+
234
+ function extensionTransitionBadgeVariant(
235
+ kind: ExtensionManagementTransition['kind'],
236
+ ): 'accent' | 'danger' | 'muted' {
237
+ if (kind === 'failed') {
238
+ return 'danger';
239
+ }
240
+ return kind === 'applied' ? 'accent' : 'muted';
241
+ }
242
+
201
243
  function ExtensionFeatureDetails({ entry }: { entry: ExtensionManagementEntry }) {
202
244
  const rows = getExtensionFeatureDetailRows(entry.features);
203
245
  const diagnostics = entry.diagnostics ?? [];
@@ -26,6 +26,7 @@ import type {
26
26
  ExtensionManagementEntry,
27
27
  ExtensionManagementFeatureSummary,
28
28
  ExtensionManagementPanelProps,
29
+ ExtensionManagementTransition,
29
30
  } from './types.js';
30
31
 
31
32
  const BROWSE_CATEGORIES = ['all', 'feature', 'editor', 'theme', 'language'] as const;
@@ -43,6 +44,7 @@ export interface ExtensionManagementSidebarProps extends ExtensionManagementPane
43
44
  emptyMarketplaceLabel?: string | undefined;
44
45
  missingExtensionIds?: readonly string[] | undefined;
45
46
  pendingAction?: ExtensionManagementPendingAction | undefined;
47
+ pendingUninstallEntryId?: string | undefined;
46
48
  }
47
49
 
48
50
  export interface ExtensionManagementPendingAction {
@@ -64,7 +66,9 @@ export function ExtensionManagementSidebar({
64
66
  onInstall,
65
67
  onRememberInstallTrust,
66
68
  onToggleEnabled,
69
+ onUninstall,
67
70
  pendingAction,
71
+ pendingUninstallEntryId,
68
72
  }: ExtensionManagementSidebarProps) {
69
73
  const [activeTab, setActiveTab] = useState<'installed' | 'marketplace'>(defaultTab);
70
74
  const [query, setQuery] = useState('');
@@ -102,7 +106,8 @@ export function ExtensionManagementSidebar({
102
106
  footer={
103
107
  <p className="workbench-extensions-sidebar__notice" role="note">
104
108
  <i aria-hidden className={cxCodicon('codicon-info')} />
105
- Installing or enabling extensions reloads the workbench to apply contributions.
109
+ Eligible unselected theme packs apply immediately. Other extension changes reload the
110
+ workbench.
106
111
  </p>
107
112
  }
108
113
  footerPlacement="overlay"
@@ -167,7 +172,9 @@ export function ExtensionManagementSidebar({
167
172
  emptyLabel={emptyInstalledLabel}
168
173
  entries={filteredInstalled}
169
174
  pendingAction={pendingAction}
175
+ pendingUninstallEntryId={pendingUninstallEntryId}
170
176
  onToggleEnabled={onToggleEnabled}
177
+ onUninstall={onUninstall}
171
178
  />
172
179
  )}
173
180
  </SideBarViewFrame>
@@ -178,12 +185,16 @@ function InstalledExtensionList({
178
185
  emptyLabel,
179
186
  entries,
180
187
  onToggleEnabled,
188
+ onUninstall,
181
189
  pendingAction,
190
+ pendingUninstallEntryId,
182
191
  }: {
183
192
  emptyLabel: string;
184
193
  entries: readonly ExtensionManagementEntry[];
185
194
  onToggleEnabled?: ExtensionManagementPanelProps['onToggleEnabled'];
195
+ onUninstall?: ExtensionManagementPanelProps['onUninstall'];
186
196
  pendingAction?: ExtensionManagementPendingAction | undefined;
197
+ pendingUninstallEntryId?: string | undefined;
187
198
  }) {
188
199
  if (entries.length === 0) {
189
200
  return (
@@ -198,6 +209,8 @@ function InstalledExtensionList({
198
209
  {entries.map((entry) => {
199
210
  const isPendingToggle =
200
211
  pendingAction?.kind === 'toggle' && pendingAction.entryId === entry.id;
212
+ const isPendingUninstall = pendingUninstallEntryId === entry.id;
213
+ const isPending = isPendingToggle || isPendingUninstall;
201
214
  const errorDiagnostics = (entry.diagnostics ?? []).filter(
202
215
  (diagnostic) => diagnostic.severity === 'error',
203
216
  );
@@ -209,15 +222,28 @@ function InstalledExtensionList({
209
222
  <ExtensionSidebarListItem
210
223
  key={entry.id}
211
224
  action={
212
- <Button
213
- compact
214
- disabled={!onToggleEnabled || entry.source === 'bundled' || isPendingToggle}
215
- type="button"
216
- variant={entry.enabled ? 'default' : 'primary'}
217
- onClick={() => onToggleEnabled?.(entry, !entry.enabled)}
218
- >
219
- {isPendingToggle ? 'Reloading…' : entry.enabled ? 'Disable' : 'Enable'}
220
- </Button>
225
+ <>
226
+ <Button
227
+ compact
228
+ disabled={!onToggleEnabled || entry.source === 'bundled' || isPending}
229
+ type="button"
230
+ variant={entry.enabled ? 'default' : 'primary'}
231
+ onClick={() => onToggleEnabled?.(entry, !entry.enabled)}
232
+ >
233
+ {isPendingToggle ? 'Reloading…' : entry.enabled ? 'Disable' : 'Enable'}
234
+ </Button>
235
+ {entry.canUninstall === true && onUninstall ? (
236
+ <Button
237
+ compact
238
+ disabled={isPending}
239
+ type="button"
240
+ variant="danger"
241
+ onClick={() => onUninstall(entry)}
242
+ >
243
+ {isPendingUninstall ? 'Reloading…' : 'Uninstall'}
244
+ </Button>
245
+ ) : null}
246
+ </>
221
247
  }
222
248
  category={entry.category}
223
249
  description={entry.description}
@@ -248,6 +274,14 @@ function InstalledExtensionList({
248
274
  {warningDiagnostics.length} warning{warningDiagnostics.length === 1 ? '' : 's'}
249
275
  </Badge>
250
276
  ) : null}
277
+ {entry.transition ? (
278
+ <Badge
279
+ title={entry.transition.message}
280
+ variant={extensionTransitionBadgeVariant(entry.transition.kind)}
281
+ >
282
+ {extensionTransitionLabel(entry.transition.kind)}
283
+ </Badge>
284
+ ) : null}
251
285
  </>
252
286
  }
253
287
  title={entry.displayName}
@@ -258,6 +292,22 @@ function InstalledExtensionList({
258
292
  );
259
293
  }
260
294
 
295
+ function extensionTransitionLabel(kind: ExtensionManagementTransition['kind']): string {
296
+ if (kind === 'reloadRequired') {
297
+ return 'Reload required';
298
+ }
299
+ return kind === 'failed' ? 'Failed' : 'Applied';
300
+ }
301
+
302
+ function extensionTransitionBadgeVariant(
303
+ kind: ExtensionManagementTransition['kind'],
304
+ ): 'accent' | 'danger' | 'muted' {
305
+ if (kind === 'failed') {
306
+ return 'danger';
307
+ }
308
+ return kind === 'applied' ? 'accent' : 'muted';
309
+ }
310
+
261
311
  function MarketplaceExtensionList({
262
312
  catalogError,
263
313
  catalogLoading,
@@ -141,4 +141,5 @@ export type {
141
141
  ExtensionManagementFeatureItem,
142
142
  ExtensionManagementFeatureSummary,
143
143
  ExtensionManagementPanelProps,
144
+ ExtensionManagementTransition,
144
145
  } from './types.js';
@@ -82,6 +82,11 @@ export interface ExtensionManagementDiagnosticSummary {
82
82
  readonly severity: 'error' | 'warning';
83
83
  }
84
84
 
85
+ export interface ExtensionManagementTransition {
86
+ readonly kind: 'applied' | 'reloadRequired' | 'failed';
87
+ readonly message: string;
88
+ }
89
+
85
90
  export interface ExtensionInstallPlanSummary {
86
91
  readonly blocked: boolean;
87
92
  readonly diagnostics?: readonly ExtensionManagementDiagnosticSummary[] | undefined;
@@ -92,6 +97,8 @@ export interface ExtensionInstallPlanSummary {
92
97
  }
93
98
 
94
99
  export interface ExtensionManagementEntry {
100
+ /** True only when a persisted installed record currently owns this entry. */
101
+ readonly canUninstall?: boolean;
95
102
  readonly category: string;
96
103
  readonly description?: string | undefined;
97
104
  readonly diagnostics?: readonly ExtensionManagementDiagnosticSummary[] | undefined;
@@ -102,6 +109,7 @@ export interface ExtensionManagementEntry {
102
109
  readonly installedAt?: string | undefined;
103
110
  readonly manifestUrl?: string | undefined;
104
111
  readonly source: ExtensionManagementEntrySource;
112
+ readonly transition?: ExtensionManagementTransition;
105
113
  }
106
114
 
107
115
  export interface ExtensionCatalogBrowseEntry {
@@ -128,6 +136,7 @@ export interface ExtensionManagementPanelProps {
128
136
  installedEntries: readonly ExtensionManagementEntry[];
129
137
  onInstall?:
130
138
  ((entry: ExtensionCatalogBrowseEntry, options?: ExtensionInstallOptions) => void) | undefined;
139
+ onUninstall?: (entry: ExtensionManagementEntry) => void;
131
140
  onToggleEnabled?: ((entry: ExtensionManagementEntry, enabled: boolean) => void) | undefined;
132
141
  /**
133
142
  * Host-owned durable trust check. When true for an entry, the install confirm
@@ -6,6 +6,7 @@ import { ScrollArea } from '../../primitives/scroll-area';
6
6
  import { cx } from '../../utils/cx';
7
7
  import { WorkbenchSectionTabPanel } from './SectionTabPanel';
8
8
  import { WorkbenchSectionedPanel } from './SectionedPanel';
9
+ import { getWorkbenchStructuredDataSchemaPathSegments } from './structuredDataSchemaSection';
9
10
  import {
10
11
  WorkbenchStructuredDataSchemaFieldInput,
11
12
  appendWorkbenchStructuredDataSchemaTableRow,
@@ -162,7 +163,10 @@ function getSchemaFieldValue({
162
163
  sectionValue: unknown;
163
164
  }) {
164
165
  const fullPath = getWorkbenchStructuredDataSchemaFieldDataPath(section, fieldPath);
165
- const rootValue = getWorkbenchStructuredDataValue(data, fullPath.split('.'));
166
+ const rootValue = getWorkbenchStructuredDataValue(
167
+ data,
168
+ getWorkbenchStructuredDataSchemaPathSegments(fullPath),
169
+ );
166
170
  if (rootValue !== null && rootValue !== undefined) return rootValue;
167
171
 
168
172
  const record = asWorkbenchStructuredDataRecord(sectionValue);
@@ -170,7 +174,8 @@ function getSchemaFieldValue({
170
174
  }
171
175
 
172
176
  function formatSchemaFieldErrorKey(path: string | readonly string[]) {
173
- const segments = typeof path === 'string' ? path.split('.') : path;
177
+ const segments =
178
+ typeof path === 'string' ? getWorkbenchStructuredDataSchemaPathSegments(path) : path;
174
179
  return segments.filter(Boolean).join('.');
175
180
  }
176
181
 
@@ -286,7 +291,11 @@ function renderSchemaFormSection({
286
291
  mergedFieldErrors: fieldErrors,
287
292
  onValidateField,
288
293
  value: fieldValue,
289
- onValueChange: (nextValue) => onDataValueChange(dataPath.split('.'), nextValue),
294
+ onValueChange: (nextValue) =>
295
+ onDataValueChange(
296
+ getWorkbenchStructuredDataSchemaPathSegments(dataPath),
297
+ nextValue,
298
+ ),
290
299
  })
291
300
  : null;
292
301
 
@@ -358,7 +367,12 @@ function renderSchemaFormSection({
358
367
  readOnly={readOnly}
359
368
  textareaClassName={classNames.settingControlTextarea}
360
369
  value={entry}
361
- onValueChange={(nextValue) => onDataValueChange(dataPath.split('.'), nextValue)}
370
+ onValueChange={(nextValue) =>
371
+ onDataValueChange(
372
+ getWorkbenchStructuredDataSchemaPathSegments(dataPath),
373
+ nextValue,
374
+ )
375
+ }
362
376
  />
363
377
  </div>
364
378
  </Field>
@@ -19,6 +19,7 @@ import {
19
19
  import {
20
20
  getWorkbenchStructuredDataSchemaFieldDataPath,
21
21
  getWorkbenchStructuredDataSchemaFieldDefinition,
22
+ getWorkbenchStructuredDataSchemaPathSegments,
22
23
  getWorkbenchStructuredDataSchemaSectionFieldLabel,
23
24
  getWorkbenchStructuredDataSchemaSectionPath,
24
25
  } from './structuredDataSchemaSection';
@@ -206,14 +207,18 @@ export function getWorkbenchStructuredDataSchemaDocumentSectionValue({
206
207
  section: WorkbenchStructuredDataSchemaSectionSummary;
207
208
  }) {
208
209
  const sectionKey = getWorkbenchStructuredDataSchemaSectionPath(section);
209
- const record = asWorkbenchStructuredDataRecord(data);
210
- if (!sectionKey || !record) return sectionKey ? null : data;
211
- if (sectionKey in record) return record[sectionKey];
210
+ if (!sectionKey) return data;
212
211
 
213
- const directValue = getWorkbenchStructuredDataValue(data, sectionKey.split('.'));
212
+ const directValue = getWorkbenchStructuredDataValue(
213
+ data,
214
+ getWorkbenchStructuredDataSchemaPathSegments(sectionKey),
215
+ );
214
216
  if (directValue !== null && directValue !== undefined) return directValue;
215
217
 
216
- for (const path of aliases[sectionKey] ?? []) {
218
+ const sectionAliases = Object.prototype.hasOwnProperty.call(aliases, sectionKey)
219
+ ? (aliases[sectionKey] ?? [])
220
+ : [];
221
+ for (const path of sectionAliases) {
217
222
  if (!path.length) return data;
218
223
  const value = getWorkbenchStructuredDataValue(data, path);
219
224
  if (value !== null && value !== undefined) return value;
@@ -279,13 +284,17 @@ export function createWorkbenchStructuredDataSchemaDocumentSampleData(
279
284
  ? section.columns
280
285
  : Object.keys(schemaTable?.items ?? {});
281
286
 
282
- return setWorkbenchStructuredDataValue(sample, path.split('.'), [
283
- createWorkbenchStructuredDataSchemaDocumentEmptyRow({
284
- columns,
285
- schema,
286
- section,
287
- }),
288
- ]);
287
+ return setWorkbenchStructuredDataValue(
288
+ sample,
289
+ getWorkbenchStructuredDataSchemaPathSegments(path),
290
+ [
291
+ createWorkbenchStructuredDataSchemaDocumentEmptyRow({
292
+ columns,
293
+ schema,
294
+ section,
295
+ }),
296
+ ],
297
+ );
289
298
  }
290
299
 
291
300
  return (section.fields ?? []).reduce<WorkbenchStructuredDataRecord>((nextSample, fieldPath) => {
@@ -296,7 +305,9 @@ export function createWorkbenchStructuredDataSchemaDocumentSampleData(
296
305
  );
297
306
  return setWorkbenchStructuredDataValue(
298
307
  nextSample,
299
- getWorkbenchStructuredDataSchemaFieldDataPath(section, fieldPath).split('.'),
308
+ getWorkbenchStructuredDataSchemaPathSegments(
309
+ getWorkbenchStructuredDataSchemaFieldDataPath(section, fieldPath),
310
+ ),
300
311
  getWorkbenchStructuredDataSchemaFieldDefaultValue(definition),
301
312
  );
302
313
  }, sample);
@@ -21,6 +21,10 @@ export function getWorkbenchStructuredDataSchemaSectionPath(
21
21
  return section.sectionKey || section.id || '';
22
22
  }
23
23
 
24
+ export function getWorkbenchStructuredDataSchemaPathSegments(path: string) {
25
+ return path ? path.split('.') : [];
26
+ }
27
+
24
28
  export function getWorkbenchStructuredDataSchemaFieldDataPath(
25
29
  section: WorkbenchStructuredDataSchemaSectionSummary,
26
30
  fieldPath: string,
@@ -7,7 +7,10 @@ import type {
7
7
  WorkbenchStructuredDataSchemaTableRowKeyInput,
8
8
  } from './structuredDataSchemaTypes';
9
9
  import { getWorkbenchStructuredDataSchemaFieldDefaultValue } from './structuredDataSchemaField';
10
- import { getWorkbenchStructuredDataSchemaSectionPath } from './structuredDataSchemaSection';
10
+ import {
11
+ getWorkbenchStructuredDataSchemaPathSegments,
12
+ getWorkbenchStructuredDataSchemaSectionPath,
13
+ } from './structuredDataSchemaSection';
11
14
 
12
15
  export function getWorkbenchStructuredDataSchemaTableRows(
13
16
  value: unknown,
@@ -34,7 +37,7 @@ export function getWorkbenchStructuredDataSchemaTablePath(
34
37
  section: WorkbenchStructuredDataSchemaSectionSummary,
35
38
  ) {
36
39
  const tablePath = getWorkbenchStructuredDataSchemaSectionPath(section);
37
- return tablePath ? tablePath.split('.') : [];
40
+ return getWorkbenchStructuredDataSchemaPathSegments(tablePath);
38
41
  }
39
42
 
40
43
  export function getWorkbenchStructuredDataSchemaTableRowKey({
@@ -73,6 +73,7 @@ export {
73
73
  } from '../editor/editorTabContextMenu';
74
74
  export type {
75
75
  CreateWorkbenchStandaloneEditorTabCommandContextInput,
76
+ CreateWorkbenchStandaloneEditorTabContextMenuItemsInput,
76
77
  WorkbenchStandaloneEditorTabLike,
77
78
  } from '../editor/editorTabContextMenu';
78
79
  export { useWorkbenchEditorTabContextMenu } from '../editor/useWorkbenchEditorTabContextMenu';
@@ -112,6 +112,8 @@
112
112
 
113
113
  .workbench-extensions-sidebar__actions {
114
114
  display: flex;
115
+ flex-wrap: wrap;
116
+ gap: 6px;
115
117
  justify-content: flex-end;
116
118
  width: 100%;
117
119
  }