@workbench-kit/shell-react 0.0.2-prototype.0.2.6

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 (93) hide show
  1. package/package.json +49 -0
  2. package/src/chat-ai-mock-proposals.ts +39 -0
  3. package/src/chat-command-input.ts +52 -0
  4. package/src/chat-command-policy.ts +54 -0
  5. package/src/chat-command-surface.tsx +155 -0
  6. package/src/chat-view-data.ts +20 -0
  7. package/src/chat-view.tsx +220 -0
  8. package/src/command-inspector-surface.tsx +46 -0
  9. package/src/commands-view-data.ts +17 -0
  10. package/src/commands-view.tsx +43 -0
  11. package/src/createCommandWorkspaceExplorerPort.ts +86 -0
  12. package/src/devtools/WorkbenchDevtoolsPanel.tsx +124 -0
  13. package/src/devtools/WorkbenchDevtoolsShell.tsx +17 -0
  14. package/src/devtools/index.ts +11 -0
  15. package/src/devtools/use-workbench-devtools-snapshot.ts +84 -0
  16. package/src/devtools/workbench-devtools-snapshot.ts +140 -0
  17. package/src/devtools/workbench-devtools.css +82 -0
  18. package/src/editor-area-dnd.ts +69 -0
  19. package/src/editor-area-model.ts +71 -0
  20. package/src/editor-area.css +454 -0
  21. package/src/editor-area.tsx +676 -0
  22. package/src/editor-host-surface.tsx +634 -0
  23. package/src/editor-pane-visibility.ts +130 -0
  24. package/src/editor-resource.ts +36 -0
  25. package/src/editor-state-storage.ts +193 -0
  26. package/src/editor-tab-context-menu.ts +118 -0
  27. package/src/editor-view-providers.tsx +552 -0
  28. package/src/editor-workspace-file-availability.ts +15 -0
  29. package/src/editor-workspace-reconcile.tsx +28 -0
  30. package/src/explorer-context-menu.ts +114 -0
  31. package/src/explorer-reveal.ts +112 -0
  32. package/src/explorer-view-data.ts +58 -0
  33. package/src/explorer-view.tsx +224 -0
  34. package/src/extension-context-menu.ts +52 -0
  35. package/src/extension-management-model.ts +313 -0
  36. package/src/extensions-view-data.ts +19 -0
  37. package/src/extensions-view.tsx +38 -0
  38. package/src/field-remap-demo.tsx +18 -0
  39. package/src/field-remap-editor-surface.tsx +36 -0
  40. package/src/field-remap-flow-adapter.ts +240 -0
  41. package/src/field-remap-flow.tsx +369 -0
  42. package/src/field-remap-graph.tsx +559 -0
  43. package/src/field-remap-panel.tsx +175 -0
  44. package/src/field-remap-samples.ts +352 -0
  45. package/src/field-remap-tree.tsx +404 -0
  46. package/src/field-remap-view-data.ts +16 -0
  47. package/src/field-remap-view.css +676 -0
  48. package/src/field-remap-view.tsx +86 -0
  49. package/src/index.ts +199 -0
  50. package/src/jdw-lab-view-data.ts +25 -0
  51. package/src/jdw-lab-view.css +21 -0
  52. package/src/jdw-lab-view.tsx +79 -0
  53. package/src/jdw-widget-form-view.tsx +65 -0
  54. package/src/jdw-widget-preview-view.tsx +51 -0
  55. package/src/json-form-source-patch.ts +323 -0
  56. package/src/jsonata-transform.ts +36 -0
  57. package/src/keybinding-management-settings.tsx +16 -0
  58. package/src/keybinding-overrides-storage.ts +57 -0
  59. package/src/management-palette-commands.ts +38 -0
  60. package/src/management-settings-ids.ts +12 -0
  61. package/src/management-settings.tsx +75 -0
  62. package/src/preference-settings-storage.ts +58 -0
  63. package/src/provider.tsx +573 -0
  64. package/src/search-view-data.ts +15 -0
  65. package/src/search-view.tsx +62 -0
  66. package/src/shell-model.tsx +149 -0
  67. package/src/shell-secondary-actions.tsx +59 -0
  68. package/src/shell-settings-constants.ts +9 -0
  69. package/src/shell-settings.tsx +599 -0
  70. package/src/shell-titlebar-layout-controls.tsx +4 -0
  71. package/src/shell-view-host.tsx +199 -0
  72. package/src/shell.tsx +649 -0
  73. package/src/table-mapping-adapter.ts +59 -0
  74. package/src/use-active-workspace-path.ts +15 -0
  75. package/src/use-command-management.ts +191 -0
  76. package/src/use-context-key-revision.ts +18 -0
  77. package/src/use-editor.ts +107 -0
  78. package/src/use-extension-management.ts +193 -0
  79. package/src/use-keybinding-management.ts +130 -0
  80. package/src/use-persisted-workbench-appearance.ts +33 -0
  81. package/src/use-workbench-chat-command-proposals.ts +194 -0
  82. package/src/use-workbench-command-descriptors.ts +41 -0
  83. package/src/workbench-appearance-storage.ts +115 -0
  84. package/src/workbench-command-host.tsx +243 -0
  85. package/src/workbench-command-palette.ts +208 -0
  86. package/src/workbench-keybinding-bridge.ts +54 -0
  87. package/src/workbench-layout-storage.ts +145 -0
  88. package/src/workbench-profile-modal.tsx +118 -0
  89. package/src/workbench-shell-command-registration.ts +58 -0
  90. package/src/workbench-startup-gate.tsx +136 -0
  91. package/src/workbench-status-sections.ts +43 -0
  92. package/src/workbench-user-commands.ts +46 -0
  93. package/src/workspace-view-state.ts +35 -0
@@ -0,0 +1,676 @@
1
+ import {
2
+ useCallback,
3
+ useMemo,
4
+ useRef,
5
+ useState,
6
+ type DragEvent as ReactDragEvent,
7
+ type MouseEvent as ReactMouseEvent,
8
+ type ReactNode,
9
+ } from 'react';
10
+ import { ContextMenu } from '@workbench-kit/react/overlay';
11
+ import { EditorTabs, type EditorTabDropPosition } from '@workbench-kit/react/primitives';
12
+ import { SplitView } from '@workbench-kit/react/workbench/split-view';
13
+ import type { WorkspaceEditorTheme } from '@workbench-kit/react/workbench/workspace/editor';
14
+ import {
15
+ createEditorGroupDropMoveOptions,
16
+ type EditorGroupDropSide,
17
+ type EditorGroupState,
18
+ type EditorLayoutNode,
19
+ } from '@workbench-kit/workbench-core';
20
+
21
+ import './editor-area.css';
22
+
23
+ import { useEditorDocumentViewProviders, useEditorService, useEditorState } from './use-editor.js';
24
+ import { useWorkbench } from './provider.js';
25
+ import { type EditorDocumentViewProvider } from './editor-view-providers.js';
26
+ import { EditorHostSurface } from './editor-host-surface.js';
27
+ import type { EditorViewMode } from './editor-pane-visibility.js';
28
+ import { createEditorTabContextMenuItems } from './editor-tab-context-menu.js';
29
+ import { pruneEditorLayout, toEditorTabModel } from './editor-area-model.js';
30
+ import {
31
+ EDITOR_TAB_DRAG_DATA_TYPE,
32
+ getEditorGroupDropSide,
33
+ isEditorTabsScrollerEventTarget,
34
+ isInternalEditorAreaDrag,
35
+ readEditorTabDragPayload,
36
+ resolveEditorTabDropTarget,
37
+ resolveEditorTabStripDropTarget,
38
+ type EditorTabDragPayload,
39
+ } from './editor-area-dnd.js';
40
+
41
+ export type { EditorViewMode } from './editor-pane-visibility.js';
42
+
43
+ export interface EditorAreaProps {
44
+ defaultViewModeForResource?: ((resourceUri: string) => EditorViewMode | undefined) | undefined;
45
+ emptyState?: ReactNode | undefined;
46
+ theme?: WorkspaceEditorTheme | undefined;
47
+ viewProviders?: readonly EditorDocumentViewProvider[] | undefined;
48
+ }
49
+
50
+ export function EditorArea({
51
+ defaultViewModeForResource,
52
+ emptyState,
53
+ theme,
54
+ viewProviders,
55
+ }: EditorAreaProps) {
56
+ const editorState = useEditorState();
57
+ const documentViewProviders = useEditorDocumentViewProviders(viewProviders);
58
+ const editorGroups = editorState.groups.filter((group) => group.tabs.length > 0);
59
+ const editorGroupsById = useMemo(
60
+ () => new Map(editorGroups.map((group) => [group.id, group])),
61
+ [editorGroups],
62
+ );
63
+ const editorLayout = useMemo(
64
+ () => pruneEditorLayout(editorState.layout, editorGroupsById),
65
+ [editorGroupsById, editorState.layout],
66
+ );
67
+ const draggedEditorTabRef = useRef<EditorTabDragPayload | null>(null);
68
+
69
+ const handleEditorTabDragStart = useCallback(
70
+ (payload: EditorTabDragPayload, event: ReactDragEvent<HTMLElement>) => {
71
+ draggedEditorTabRef.current = payload;
72
+ event.dataTransfer.effectAllowed = 'move';
73
+ event.dataTransfer.setData(EDITOR_TAB_DRAG_DATA_TYPE, JSON.stringify(payload));
74
+ },
75
+ [],
76
+ );
77
+
78
+ const handleEditorTabDragEnd = useCallback(() => {
79
+ draggedEditorTabRef.current = null;
80
+ }, []);
81
+
82
+ const getDraggedEditorTab = useCallback((event?: ReactDragEvent<HTMLElement>) => {
83
+ if (draggedEditorTabRef.current) {
84
+ return draggedEditorTabRef.current;
85
+ }
86
+
87
+ return event ? readEditorTabDragPayload(event.dataTransfer) : null;
88
+ }, []);
89
+
90
+ if (editorGroups.length === 0 || !editorLayout) {
91
+ return (
92
+ <main aria-label="Editor area" className="workbench-editor-area workbench-editor-area--empty">
93
+ {emptyState ?? (
94
+ <section className="workbench-editor-area__empty">
95
+ <p>No editors open</p>
96
+ </section>
97
+ )}
98
+ </main>
99
+ );
100
+ }
101
+
102
+ return (
103
+ <main aria-label="Editor area" className="workbench-editor-area">
104
+ <div className="workbench-editor-area__content">
105
+ <EditorGroupSplit
106
+ activeGroupId={editorState.activeGroupId}
107
+ defaultViewModeForResource={defaultViewModeForResource}
108
+ getDraggedEditorTab={getDraggedEditorTab}
109
+ groupsById={editorGroupsById}
110
+ layout={editorLayout}
111
+ layoutPath={[]}
112
+ onEditorTabDragEnd={handleEditorTabDragEnd}
113
+ onEditorTabDragStart={handleEditorTabDragStart}
114
+ theme={theme}
115
+ viewProviders={documentViewProviders}
116
+ />
117
+ </div>
118
+ </main>
119
+ );
120
+ }
121
+
122
+ function EditorGroupSplit({
123
+ activeGroupId,
124
+ defaultViewModeForResource,
125
+ getDraggedEditorTab,
126
+ groupsById,
127
+ layout,
128
+ layoutPath,
129
+ onEditorTabDragEnd,
130
+ onEditorTabDragStart,
131
+ theme,
132
+ viewProviders,
133
+ }: {
134
+ activeGroupId: string | undefined;
135
+ defaultViewModeForResource: ((resourceUri: string) => EditorViewMode | undefined) | undefined;
136
+ getDraggedEditorTab: (event?: ReactDragEvent<HTMLElement>) => EditorTabDragPayload | null;
137
+ groupsById: ReadonlyMap<string, EditorGroupState>;
138
+ layout: EditorLayoutNode;
139
+ layoutPath: readonly number[] | undefined;
140
+ onEditorTabDragEnd: () => void;
141
+ onEditorTabDragStart: (payload: EditorTabDragPayload, event: ReactDragEvent<HTMLElement>) => void;
142
+ theme: WorkspaceEditorTheme | undefined;
143
+ viewProviders: readonly EditorDocumentViewProvider[];
144
+ }) {
145
+ const editorService = useEditorService();
146
+
147
+ if (layout.type === 'group') {
148
+ const group = groupsById.get(layout.groupId);
149
+ if (!group) {
150
+ return null;
151
+ }
152
+
153
+ return (
154
+ <EditorGroupPane
155
+ active={group.id === activeGroupId}
156
+ defaultViewModeForResource={defaultViewModeForResource}
157
+ getDraggedEditorTab={getDraggedEditorTab}
158
+ group={group}
159
+ onEditorTabDragEnd={onEditorTabDragEnd}
160
+ onEditorTabDragStart={onEditorTabDragStart}
161
+ theme={theme}
162
+ viewProviders={viewProviders}
163
+ />
164
+ );
165
+ }
166
+
167
+ const [primaryLayout, ...secondaryLayouts] = layout.children;
168
+ if (!primaryLayout) {
169
+ return null;
170
+ }
171
+
172
+ if (secondaryLayouts.length === 0) {
173
+ return (
174
+ <EditorGroupSplit
175
+ activeGroupId={activeGroupId}
176
+ defaultViewModeForResource={defaultViewModeForResource}
177
+ getDraggedEditorTab={getDraggedEditorTab}
178
+ groupsById={groupsById}
179
+ layout={primaryLayout}
180
+ layoutPath={layoutPath ? [...layoutPath, 0] : undefined}
181
+ onEditorTabDragEnd={onEditorTabDragEnd}
182
+ onEditorTabDragStart={onEditorTabDragStart}
183
+ theme={theme}
184
+ viewProviders={viewProviders}
185
+ />
186
+ );
187
+ }
188
+
189
+ const secondaryLayout: EditorLayoutNode =
190
+ secondaryLayouts.length === 1
191
+ ? secondaryLayouts[0]
192
+ : {
193
+ children: secondaryLayouts,
194
+ direction: layout.direction,
195
+ type: 'split',
196
+ };
197
+ const secondaryLayoutPath =
198
+ secondaryLayouts.length === 1 && layoutPath ? [...layoutPath, 1] : undefined;
199
+
200
+ return (
201
+ <SplitView
202
+ className="workbench-editor-area__group-split"
203
+ defaultPrimarySizePercent={100 / layout.children.length}
204
+ minPrimarySizePercent={20}
205
+ orientation={layout.direction}
206
+ primarySizePercent={layout.primarySizePercent}
207
+ onPrimarySizePercentChange={
208
+ layoutPath
209
+ ? (primarySizePercent) =>
210
+ editorService.setEditorSplitPrimarySize({
211
+ path: layoutPath,
212
+ primarySizePercent,
213
+ })
214
+ : undefined
215
+ }
216
+ primary={
217
+ <EditorGroupSplit
218
+ activeGroupId={activeGroupId}
219
+ defaultViewModeForResource={defaultViewModeForResource}
220
+ getDraggedEditorTab={getDraggedEditorTab}
221
+ groupsById={groupsById}
222
+ layout={primaryLayout}
223
+ layoutPath={layoutPath ? [...layoutPath, 0] : undefined}
224
+ onEditorTabDragEnd={onEditorTabDragEnd}
225
+ onEditorTabDragStart={onEditorTabDragStart}
226
+ theme={theme}
227
+ viewProviders={viewProviders}
228
+ />
229
+ }
230
+ secondary={
231
+ <EditorGroupSplit
232
+ activeGroupId={activeGroupId}
233
+ defaultViewModeForResource={defaultViewModeForResource}
234
+ getDraggedEditorTab={getDraggedEditorTab}
235
+ groupsById={groupsById}
236
+ layout={secondaryLayout}
237
+ layoutPath={secondaryLayoutPath}
238
+ onEditorTabDragEnd={onEditorTabDragEnd}
239
+ onEditorTabDragStart={onEditorTabDragStart}
240
+ theme={theme}
241
+ viewProviders={viewProviders}
242
+ />
243
+ }
244
+ />
245
+ );
246
+ }
247
+
248
+ function EditorGroupPane({
249
+ active,
250
+ defaultViewModeForResource,
251
+ getDraggedEditorTab,
252
+ group,
253
+ onEditorTabDragEnd,
254
+ onEditorTabDragStart,
255
+ theme,
256
+ viewProviders,
257
+ }: {
258
+ active: boolean;
259
+ defaultViewModeForResource: ((resourceUri: string) => EditorViewMode | undefined) | undefined;
260
+ getDraggedEditorTab: (event?: ReactDragEvent<HTMLElement>) => EditorTabDragPayload | null;
261
+ group: EditorGroupState;
262
+ onEditorTabDragEnd: () => void;
263
+ onEditorTabDragStart: (payload: EditorTabDragPayload, event: ReactDragEvent<HTMLElement>) => void;
264
+ theme: WorkspaceEditorTheme | undefined;
265
+ viewProviders: readonly EditorDocumentViewProvider[];
266
+ }) {
267
+ const editorService = useEditorService();
268
+ const { executeCommand, extensionRegistry } = useWorkbench();
269
+ const tabs = group.tabs;
270
+ const activeTabId = group.activeTabId ?? tabs[0]?.id ?? '';
271
+ const activeTab = tabs.find((tab) => tab.id === activeTabId) ?? tabs[0];
272
+ const [modeToolbarHost, setModeToolbarHost] = useState<HTMLDivElement | null>(null);
273
+ const [modeToolbarVisible, setModeToolbarVisible] = useState(false);
274
+ const [dropSide, setDropSide] = useState<EditorGroupDropSide | null>(null);
275
+ const [tabDropTarget, setTabDropTarget] = useState<{
276
+ position: EditorTabDropPosition;
277
+ tabId: string;
278
+ } | null>(null);
279
+ const [tabContextMenu, setTabContextMenu] = useState<{
280
+ tabId: string;
281
+ x: number;
282
+ y: number;
283
+ } | null>(null);
284
+ const editorTabs = useMemo(
285
+ () =>
286
+ tabs.map((tab) =>
287
+ toEditorTabModel(tab, tabDropTarget?.tabId === tab.id ? tabDropTarget.position : undefined),
288
+ ),
289
+ [tabDropTarget, tabs],
290
+ );
291
+
292
+ const handleModeToolbarHost = useCallback((node: HTMLDivElement | null) => {
293
+ setModeToolbarHost(node);
294
+ }, []);
295
+ const handleModeToolbarVisibleChange = useCallback((visible: boolean) => {
296
+ setModeToolbarVisible(visible);
297
+ }, []);
298
+ const handleTabContextMenu = useCallback(
299
+ (tabId: string, event: ReactMouseEvent<HTMLElement>) => {
300
+ event.preventDefault();
301
+ editorService.setActiveEditor(tabId);
302
+ setTabContextMenu({ tabId, x: event.clientX, y: event.clientY });
303
+ },
304
+ [editorService],
305
+ );
306
+
307
+ const handleTabDragStart = useCallback(
308
+ (tabId: string, event: ReactDragEvent<HTMLElement>) => {
309
+ onEditorTabDragStart({ groupId: group.id, tabId }, event);
310
+ editorService.setActiveEditor(tabId);
311
+ },
312
+ [editorService, group.id, onEditorTabDragStart],
313
+ );
314
+
315
+ const clearDragFeedback = useCallback(() => {
316
+ setDropSide(null);
317
+ setTabDropTarget(null);
318
+ }, []);
319
+
320
+ const finishEditorTabDrag = useCallback(() => {
321
+ onEditorTabDragEnd();
322
+ clearDragFeedback();
323
+ }, [clearDragFeedback, onEditorTabDragEnd]);
324
+
325
+ const handleTabDragEnd = useCallback(
326
+ (_tabId: string, _event: ReactDragEvent<HTMLElement>) => {
327
+ finishEditorTabDrag();
328
+ },
329
+ [finishEditorTabDrag],
330
+ );
331
+
332
+ const handleTabDoubleClick = useCallback(
333
+ (tabId: string) => {
334
+ editorService.pinEditor(tabId);
335
+ },
336
+ [editorService],
337
+ );
338
+
339
+ const handleTabDragOver = useCallback(
340
+ (targetTabId: string, event: ReactDragEvent<HTMLElement>) => {
341
+ const draggedTab = getDraggedEditorTab(event);
342
+ if (!draggedTab) {
343
+ setTabDropTarget(null);
344
+ return;
345
+ }
346
+
347
+ event.preventDefault();
348
+ event.stopPropagation();
349
+ if (draggedTab.tabId === targetTabId) {
350
+ event.dataTransfer.dropEffect = 'none';
351
+ clearDragFeedback();
352
+ return;
353
+ }
354
+
355
+ event.dataTransfer.dropEffect = 'move';
356
+ setDropSide(null);
357
+ const dropTarget = resolveEditorTabDropTarget({
358
+ clientX: event.clientX,
359
+ draggedTab,
360
+ groupId: group.id,
361
+ tabs,
362
+ target: event.currentTarget,
363
+ targetTabId,
364
+ });
365
+ if (!dropTarget) {
366
+ event.dataTransfer.dropEffect = 'none';
367
+ setTabDropTarget(null);
368
+ return;
369
+ }
370
+
371
+ setTabDropTarget({
372
+ position: dropTarget.position,
373
+ tabId: dropTarget.tabId,
374
+ });
375
+ },
376
+ [clearDragFeedback, getDraggedEditorTab, group.id, tabs],
377
+ );
378
+
379
+ const handleTabDrop = useCallback(
380
+ (targetTabId: string, event: ReactDragEvent<HTMLElement>) => {
381
+ const draggedTab = getDraggedEditorTab(event);
382
+ if (!draggedTab) {
383
+ setTabDropTarget(null);
384
+ return;
385
+ }
386
+
387
+ event.preventDefault();
388
+ event.stopPropagation();
389
+ if (draggedTab.tabId === targetTabId) {
390
+ finishEditorTabDrag();
391
+ return;
392
+ }
393
+
394
+ const dropTarget = resolveEditorTabDropTarget({
395
+ clientX: event.clientX,
396
+ draggedTab,
397
+ groupId: group.id,
398
+ preferredPosition:
399
+ tabDropTarget?.tabId === targetTabId ? tabDropTarget.position : undefined,
400
+ tabs,
401
+ target: event.currentTarget,
402
+ targetTabId,
403
+ });
404
+ if (!dropTarget) {
405
+ finishEditorTabDrag();
406
+ return;
407
+ }
408
+
409
+ editorService.moveEditor({
410
+ groupId: group.id,
411
+ tabId: draggedTab.tabId,
412
+ targetIndex: dropTarget.targetIndex,
413
+ });
414
+ finishEditorTabDrag();
415
+ },
416
+ [editorService, finishEditorTabDrag, getDraggedEditorTab, group.id, tabDropTarget, tabs],
417
+ );
418
+
419
+ const handleTabStripDragOver = useCallback(
420
+ (event: ReactDragEvent<HTMLElement>) => {
421
+ const draggedTab = getDraggedEditorTab(event);
422
+ if (!draggedTab || !isEditorTabsScrollerEventTarget(event.target)) {
423
+ return;
424
+ }
425
+
426
+ event.preventDefault();
427
+ event.stopPropagation();
428
+ setDropSide(null);
429
+ const dropTarget = resolveEditorTabStripDropTarget({
430
+ draggedTab,
431
+ groupId: group.id,
432
+ tabs,
433
+ });
434
+ if (!dropTarget) {
435
+ event.dataTransfer.dropEffect = 'none';
436
+ setTabDropTarget(null);
437
+ return;
438
+ }
439
+
440
+ event.dataTransfer.dropEffect = 'move';
441
+ setTabDropTarget({
442
+ position: dropTarget.position,
443
+ tabId: dropTarget.tabId,
444
+ });
445
+ },
446
+ [getDraggedEditorTab, group.id, tabs],
447
+ );
448
+
449
+ const handleTabStripDrop = useCallback(
450
+ (event: ReactDragEvent<HTMLElement>) => {
451
+ const draggedTab = getDraggedEditorTab(event);
452
+ if (!draggedTab || !isEditorTabsScrollerEventTarget(event.target)) {
453
+ return;
454
+ }
455
+
456
+ event.preventDefault();
457
+ event.stopPropagation();
458
+ const dropTarget = resolveEditorTabStripDropTarget({
459
+ draggedTab,
460
+ groupId: group.id,
461
+ tabs,
462
+ });
463
+ if (!dropTarget) {
464
+ finishEditorTabDrag();
465
+ return;
466
+ }
467
+
468
+ editorService.moveEditor({
469
+ groupId: group.id,
470
+ tabId: draggedTab.tabId,
471
+ targetIndex: dropTarget.targetIndex,
472
+ });
473
+ finishEditorTabDrag();
474
+ },
475
+ [editorService, finishEditorTabDrag, getDraggedEditorTab, group.id, tabs],
476
+ );
477
+
478
+ const handleTabDragLeave = useCallback(
479
+ (targetTabId: string, event: ReactDragEvent<HTMLElement>) => {
480
+ const nextTarget = event.relatedTarget;
481
+ if (nextTarget instanceof Node && event.currentTarget.contains(nextTarget)) {
482
+ return;
483
+ }
484
+
485
+ setTabDropTarget((current) => (current?.tabId === targetTabId ? null : current));
486
+ },
487
+ [],
488
+ );
489
+
490
+ const handleGroupDragOver = useCallback(
491
+ (event: ReactDragEvent<HTMLElement>) => {
492
+ if (!getDraggedEditorTab(event)) {
493
+ return;
494
+ }
495
+
496
+ event.preventDefault();
497
+ event.dataTransfer.dropEffect = 'move';
498
+ setTabDropTarget(null);
499
+ setDropSide(getEditorGroupDropSide(event.currentTarget, event.clientX, event.clientY));
500
+ },
501
+ [getDraggedEditorTab],
502
+ );
503
+
504
+ const handleGroupBodyDragOverCapture = useCallback(
505
+ (event: ReactDragEvent<HTMLElement>) => {
506
+ const draggedTab = getDraggedEditorTab(event);
507
+ if (!isInternalEditorAreaDrag(event.dataTransfer, draggedTab)) {
508
+ return;
509
+ }
510
+
511
+ event.preventDefault();
512
+ event.stopPropagation();
513
+ setTabDropTarget(null);
514
+
515
+ if (!draggedTab) {
516
+ event.dataTransfer.dropEffect = 'none';
517
+ setDropSide(null);
518
+ return;
519
+ }
520
+
521
+ event.dataTransfer.dropEffect = 'move';
522
+ setDropSide(getEditorGroupDropSide(event.currentTarget, event.clientX, event.clientY));
523
+ },
524
+ [getDraggedEditorTab],
525
+ );
526
+
527
+ const handleGroupDrop = useCallback(
528
+ (event: ReactDragEvent<HTMLElement>) => {
529
+ const draggedTab = getDraggedEditorTab(event);
530
+ if (!draggedTab) {
531
+ setDropSide(null);
532
+ return;
533
+ }
534
+
535
+ const targetDropSide =
536
+ dropSide ?? getEditorGroupDropSide(event.currentTarget, event.clientX, event.clientY);
537
+ event.preventDefault();
538
+ editorService.moveEditor(
539
+ createEditorGroupDropMoveOptions({
540
+ tabId: draggedTab.tabId,
541
+ groupId: group.id,
542
+ dropSide: targetDropSide,
543
+ }),
544
+ );
545
+ finishEditorTabDrag();
546
+ },
547
+ [dropSide, editorService, finishEditorTabDrag, getDraggedEditorTab, group.id],
548
+ );
549
+
550
+ const handleGroupBodyDropCapture = useCallback(
551
+ (event: ReactDragEvent<HTMLElement>) => {
552
+ const draggedTab = getDraggedEditorTab(event);
553
+ if (!isInternalEditorAreaDrag(event.dataTransfer, draggedTab)) {
554
+ return;
555
+ }
556
+
557
+ event.preventDefault();
558
+ event.stopPropagation();
559
+ setTabDropTarget(null);
560
+
561
+ if (!draggedTab) {
562
+ setDropSide(null);
563
+ return;
564
+ }
565
+
566
+ const targetDropSide =
567
+ dropSide ?? getEditorGroupDropSide(event.currentTarget, event.clientX, event.clientY);
568
+ editorService.moveEditor(
569
+ createEditorGroupDropMoveOptions({
570
+ tabId: draggedTab.tabId,
571
+ groupId: group.id,
572
+ dropSide: targetDropSide,
573
+ }),
574
+ );
575
+ finishEditorTabDrag();
576
+ },
577
+ [dropSide, editorService, finishEditorTabDrag, getDraggedEditorTab, group.id],
578
+ );
579
+
580
+ const handleGroupDragLeave = useCallback(
581
+ (event: ReactDragEvent<HTMLElement>) => {
582
+ const nextTarget = event.relatedTarget;
583
+ if (nextTarget instanceof Node && event.currentTarget.contains(nextTarget)) {
584
+ return;
585
+ }
586
+
587
+ clearDragFeedback();
588
+ },
589
+ [clearDragFeedback],
590
+ );
591
+
592
+ const contextTab = tabContextMenu
593
+ ? tabs.find((tab) => tab.id === tabContextMenu.tabId)
594
+ : undefined;
595
+
596
+ return (
597
+ <section
598
+ aria-label={active ? 'Active editor group' : 'Editor group'}
599
+ className={[
600
+ 'workbench-editor-area__group-pane',
601
+ active ? 'workbench-editor-area__group-pane--active' : '',
602
+ ]
603
+ .filter(Boolean)
604
+ .join(' ')}
605
+ data-editor-group-id={group.id}
606
+ data-drop-side={dropSide ?? undefined}
607
+ onDragLeave={handleGroupDragLeave}
608
+ onDragOver={handleGroupDragOver}
609
+ onDrop={handleGroupDrop}
610
+ >
611
+ {dropSide ? <div aria-hidden className="workbench-editor-area__drop-overlay" /> : null}
612
+ <EditorTabs
613
+ activeId={activeTabId}
614
+ draggableTabs
615
+ aria-label={active ? 'Active editor group tabs' : 'Editor group tabs'}
616
+ onClose={(tabId) => {
617
+ editorService.closeEditor(tabId);
618
+ }}
619
+ onSelect={(tabId) => {
620
+ editorService.setActiveEditor(tabId);
621
+ }}
622
+ onTabContextMenu={handleTabContextMenu}
623
+ onTabDoubleClick={handleTabDoubleClick}
624
+ onTabDragEnd={handleTabDragEnd}
625
+ onTabDragLeave={handleTabDragLeave}
626
+ onTabDragOver={handleTabDragOver}
627
+ onTabDragStart={handleTabDragStart}
628
+ onTabDrop={handleTabDrop}
629
+ onDragOver={handleTabStripDragOver}
630
+ onDrop={handleTabStripDrop}
631
+ addons={
632
+ activeTab && modeToolbarVisible ? (
633
+ <div className="workbench-editor-area__group-actions">
634
+ <div
635
+ ref={handleModeToolbarHost}
636
+ className="workbench-editor-area__mode-toolbar-outlet"
637
+ />
638
+ </div>
639
+ ) : undefined
640
+ }
641
+ tabs={editorTabs}
642
+ />
643
+ <div
644
+ className="workbench-editor-area__group-body"
645
+ onDragEnterCapture={handleGroupBodyDragOverCapture}
646
+ onDragOverCapture={handleGroupBodyDragOverCapture}
647
+ onDropCapture={handleGroupBodyDropCapture}
648
+ >
649
+ <EditorHostSurface
650
+ activeTab={activeTab}
651
+ defaultViewModeForResource={defaultViewModeForResource}
652
+ modeToolbarHost={modeToolbarHost}
653
+ onModeToolbarVisibleChange={handleModeToolbarVisibleChange}
654
+ theme={theme}
655
+ viewProviders={viewProviders}
656
+ />
657
+ </div>
658
+ {tabContextMenu && contextTab ? (
659
+ <ContextMenu
660
+ ariaLabel="Editor tab menu"
661
+ items={createEditorTabContextMenuItems({
662
+ editorService,
663
+ executeExtensionCommand: (commandId) => executeCommand(commandId),
664
+ extensionRegistry,
665
+ groupId: group.id,
666
+ tab: contextTab,
667
+ tabs,
668
+ })}
669
+ x={tabContextMenu.x}
670
+ y={tabContextMenu.y}
671
+ onClose={() => setTabContextMenu(null)}
672
+ />
673
+ ) : null}
674
+ </section>
675
+ );
676
+ }