gloomberb 0.9.2 → 0.9.3
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 +23 -2
- package/bin/gloomberb +1 -1
- package/package.json +1 -1
- package/src/app.tsx +10 -7
- package/src/capabilities/registry.ts +1 -0
- package/src/capabilities/types.ts +23 -0
- package/src/cli/commands/api.ts +119 -0
- package/src/cli/commands/automation.ts +173 -0
- package/src/cli/commands/command-utils.ts +53 -0
- package/src/cli/commands/market.ts +527 -0
- package/src/cli/commands/overview.ts +222 -0
- package/src/cli/commands/search.ts +39 -0
- package/src/cli/commands/system.ts +553 -0
- package/src/cli/commands/ticker.ts +104 -12
- package/src/cli/context.ts +35 -2
- package/src/cli/entry.ts +51 -0
- package/src/cli/errors.ts +56 -5
- package/src/cli/index.ts +73 -11
- package/src/cli/options.test.ts +38 -0
- package/src/cli/options.ts +95 -0
- package/src/cli/pane-functions/index.ts +10 -1
- package/src/cli/registry.test.ts +40 -0
- package/src/cli/registry.ts +46 -5
- package/src/cli/result.test.ts +82 -0
- package/src/cli/result.ts +149 -0
- package/src/cli/types.ts +11 -0
- package/src/cli.test.ts +11 -11
- package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
- package/src/components/chart/comparison-stock-chart/legend.tsx +4 -0
- package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
- package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
- package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
- package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
- package/src/components/chart/comparison-stock-chart/view.tsx +12 -2
- package/src/components/chart/core/pane-settings.test.ts +27 -0
- package/src/components/chart/core/pane-settings.ts +60 -1
- package/src/components/chart/core/pointer.ts +2 -0
- package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
- package/src/components/chart/stock-chart/settings.ts +7 -3
- package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
- package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
- package/src/components/command-bar/surface/pane.test.tsx +76 -0
- package/src/components/data-table/view.tsx +13 -2
- package/src/components/feed-data-table/stack-view.tsx +9 -1
- package/src/components/index.ts +1 -1
- package/src/components/input-search-bar.test.tsx +103 -0
- package/src/components/input-search-bar.tsx +16 -0
- package/src/components/layout/shell/active-drag.ts +5 -1
- package/src/components/layout/shell/drag/runtime.ts +21 -3
- package/src/components/layout/shell/fullscreen.ts +10 -0
- package/src/components/layout/shell/index.test.tsx +395 -2
- package/src/components/layout/shell/index.tsx +128 -2
- package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
- package/src/components/layout/shell/pane/layers.tsx +26 -14
- package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
- package/src/components/layout/shell/shortcuts.ts +4 -0
- package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
- package/src/components/layout/shell/window-mode/index.ts +11 -3
- package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
- package/src/components/layout/status-bar.test.tsx +105 -0
- package/src/components/layout/status-bar.tsx +32 -6
- package/src/components/layout/transient-layout.tsx +57 -0
- package/src/components/layout/window-edit/mode.test.ts +76 -5
- package/src/components/layout/window-edit/mode.ts +47 -2
- package/src/components/layout/window-edit/presentation.ts +2 -1
- package/src/components/price-performance.tsx +9 -1
- package/src/core/app-services.ts +7 -4
- package/src/data/config/store/index.ts +1 -2
- package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
- package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
- package/src/plugins/builtin/comparison-chart/index.test.tsx +130 -6
- package/src/plugins/builtin/news/wire/read-state.ts +9 -32
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
- package/src/plugins/builtin/shared/read-state.ts +43 -0
- package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
- package/src/plugins/builtin/substack/article-detail.tsx +39 -15
- package/src/plugins/builtin/substack/article-stack.tsx +6 -2
- package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
- package/src/plugins/builtin/substack/pane.tsx +8 -3
- package/src/plugins/builtin/substack/read-state.ts +55 -0
- package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
- package/src/plugins/pane-manager/docking.ts +9 -2
- package/src/plugins/pane-manager/queries.ts +8 -2
- package/src/plugins/prediction-markets/controller/index.ts +2 -0
- package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
- package/src/plugins/prediction-markets/pane.test.tsx +32 -0
- package/src/plugins/prediction-markets/pane.tsx +21 -1
- package/src/plugins/prediction-markets/test-helpers.tsx +1 -0
- package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/index.ts +13 -0
- package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
- package/src/renderers/electrobun/bun/window/focus.ts +5 -0
- package/src/renderers/electrobun/view/app-services.ts +6 -4
- package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
- package/src/renderers/electrobun/view/host/text.test.ts +22 -0
- package/src/renderers/electrobun/view/host/text.tsx +24 -20
- package/src/renderers/electrobun/view/ui-host.tsx +1 -1
- package/src/renderers/opentui/start.tsx +13 -5
- package/src/state/app/input-capture.ts +12 -0
- package/src/types/plugin.ts +28 -0
- package/src/utils/cli-output.ts +6 -0
- package/src/utils/search-focus-navigation.ts +24 -0
- package/src/version.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AsciiText, Box, Text, compactContextMenuItems, useContextMenu, useUiHost } from "../../../ui";
|
|
2
|
-
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { useRendererHost, useUiCapabilities } from "../../../ui";
|
|
4
4
|
import { useViewport } from "../../../react/input";
|
|
5
5
|
import { useDialogState } from "../../../ui/dialog";
|
|
@@ -8,6 +8,7 @@ import type { DesktopDockPreviewState, DesktopWindowBridge } from "../../../type
|
|
|
8
8
|
import {
|
|
9
9
|
getDockDividerLayouts,
|
|
10
10
|
getDockLeafLayouts,
|
|
11
|
+
isPaneInLayout,
|
|
11
12
|
type DockGeometryOptions,
|
|
12
13
|
type LayoutBounds,
|
|
13
14
|
type ResolvedPane,
|
|
@@ -58,6 +59,8 @@ import {
|
|
|
58
59
|
useShellVisibleLayout,
|
|
59
60
|
} from "./layout-state";
|
|
60
61
|
import { useShellPaneActions } from "./pane/actions";
|
|
62
|
+
import { resolvePaneFocusSourceLayout } from "./fullscreen";
|
|
63
|
+
import { useTransientLayout } from "../transient-layout";
|
|
61
64
|
|
|
62
65
|
export { resolveAppHeaderHeightCells } from "./chrome";
|
|
63
66
|
export { buildNativeWindowState } from "./native/window-state";
|
|
@@ -70,6 +73,13 @@ interface ShellProps {
|
|
|
70
73
|
commandBarNativeOccluder?: LayoutBounds | null;
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
interface TransientFocusLayoutState {
|
|
77
|
+
paneId: string;
|
|
78
|
+
layout: LayoutConfig;
|
|
79
|
+
sourceLayoutIndex: number;
|
|
80
|
+
active: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
73
83
|
export function Shell({
|
|
74
84
|
pluginRegistry,
|
|
75
85
|
desktopWindowBridge,
|
|
@@ -86,6 +96,7 @@ export function Shell({
|
|
|
86
96
|
const inputCaptured = useAppSelector((state) => state.inputCaptured);
|
|
87
97
|
const statusBarVisible = useAppSelector(selectStatusBarVisible);
|
|
88
98
|
const rendererHost = useRendererHost();
|
|
99
|
+
const { setTransientLayout } = useTransientLayout();
|
|
89
100
|
const uiKind = useUiHost().kind;
|
|
90
101
|
const shortcutDisplayMode = getShortcutDisplayMode(uiKind);
|
|
91
102
|
const { nativePaneChrome = false, nativeContextMenu, precisePointer, titleBarOverlay, cellHeightPx } = useUiCapabilities();
|
|
@@ -103,6 +114,9 @@ export function Shell({
|
|
|
103
114
|
setHoveredPaneId((current) => (current === paneId ? current : paneId));
|
|
104
115
|
}, []);
|
|
105
116
|
const [menuState, setMenuState] = useState<ActionMenuState | null>(null);
|
|
117
|
+
const [transientFocusLayoutState, setTransientFocusLayoutState] = useState<TransientFocusLayoutState | null>(null);
|
|
118
|
+
const transientFocusLayoutStateRef = useRef<TransientFocusLayoutState | null>(null);
|
|
119
|
+
transientFocusLayoutStateRef.current = transientFocusLayoutState;
|
|
106
120
|
const [hoveredMenuItemId, setHoveredMenuItemId] = useState<string | null>(null);
|
|
107
121
|
const closePaneMenu = useCallback(() => {
|
|
108
122
|
setMenuState(null);
|
|
@@ -149,7 +163,7 @@ export function Shell({
|
|
|
149
163
|
}, [dispatch]);
|
|
150
164
|
|
|
151
165
|
const {
|
|
152
|
-
activeLayout,
|
|
166
|
+
activeLayout: windowModeLayout,
|
|
153
167
|
nativeWindowModePanelRect,
|
|
154
168
|
selectWindowModePane,
|
|
155
169
|
startWindowMode,
|
|
@@ -171,6 +185,17 @@ export function Shell({
|
|
|
171
185
|
visibleLayout,
|
|
172
186
|
width,
|
|
173
187
|
});
|
|
188
|
+
const transientFocusActive = !windowMode && transientFocusLayoutState?.active === true;
|
|
189
|
+
const activeLayout = transientFocusActive && transientFocusLayoutState
|
|
190
|
+
? transientFocusLayoutState.layout
|
|
191
|
+
: windowModeLayout;
|
|
192
|
+
const transientFocusPaneId = transientFocusActive ? transientFocusLayoutState?.paneId ?? null : null;
|
|
193
|
+
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (!windowMode || !transientFocusLayoutState) return;
|
|
196
|
+
transientFocusLayoutStateRef.current = null;
|
|
197
|
+
setTransientFocusLayoutState(null);
|
|
198
|
+
}, [transientFocusLayoutState, windowMode]);
|
|
174
199
|
|
|
175
200
|
const {
|
|
176
201
|
dockedPanes,
|
|
@@ -211,6 +236,102 @@ export function Shell({
|
|
|
211
236
|
visibleLayout,
|
|
212
237
|
width,
|
|
213
238
|
});
|
|
239
|
+
const setTransientFocusLayout = useCallback((next: TransientFocusLayoutState | null) => {
|
|
240
|
+
transientFocusLayoutStateRef.current = next;
|
|
241
|
+
setTransientFocusLayoutState(next);
|
|
242
|
+
}, []);
|
|
243
|
+
const activateTransientFocusState = useCallback((current: TransientFocusLayoutState) => {
|
|
244
|
+
closePaneMenu();
|
|
245
|
+
setTransientFocusLayout({ ...current, active: true });
|
|
246
|
+
const sourceLayout = config.layouts[current.sourceLayoutIndex]?.layout;
|
|
247
|
+
if (
|
|
248
|
+
current.sourceLayoutIndex !== config.activeLayoutIndex
|
|
249
|
+
&& sourceLayout
|
|
250
|
+
&& isPaneInLayout(sourceLayout, current.paneId)
|
|
251
|
+
) {
|
|
252
|
+
dispatch({ type: "SWITCH_LAYOUT", index: current.sourceLayoutIndex });
|
|
253
|
+
}
|
|
254
|
+
focusPane(current.paneId);
|
|
255
|
+
}, [closePaneMenu, config.activeLayoutIndex, config.layouts, dispatch, focusPane, setTransientFocusLayout]);
|
|
256
|
+
const toggleFocusedPaneFullscreen = useCallback(() => {
|
|
257
|
+
const current = transientFocusLayoutStateRef.current;
|
|
258
|
+
if (current?.active) {
|
|
259
|
+
setTransientFocusLayout(null);
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (
|
|
264
|
+
current
|
|
265
|
+
&& current.paneId === focusedPaneId
|
|
266
|
+
&& current.sourceLayoutIndex === config.activeLayoutIndex
|
|
267
|
+
) {
|
|
268
|
+
activateTransientFocusState(current);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const nextLayout = resolvePaneFocusSourceLayout(visibleLayout, focusedPaneId);
|
|
273
|
+
if (!focusedPaneId || !nextLayout) {
|
|
274
|
+
pluginRegistry.notify({ body: "Focus a pane to make it fullscreen", type: "info" });
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
closePaneMenu();
|
|
279
|
+
setTransientFocusLayout({
|
|
280
|
+
paneId: focusedPaneId,
|
|
281
|
+
layout: nextLayout,
|
|
282
|
+
sourceLayoutIndex: config.activeLayoutIndex,
|
|
283
|
+
active: true,
|
|
284
|
+
});
|
|
285
|
+
focusPane(focusedPaneId);
|
|
286
|
+
return true;
|
|
287
|
+
}, [
|
|
288
|
+
activateTransientFocusState,
|
|
289
|
+
closePaneMenu,
|
|
290
|
+
config.activeLayoutIndex,
|
|
291
|
+
focusedPaneId,
|
|
292
|
+
focusPane,
|
|
293
|
+
pluginRegistry,
|
|
294
|
+
setTransientFocusLayout,
|
|
295
|
+
visibleLayout,
|
|
296
|
+
]);
|
|
297
|
+
const activateTransientFocusLayout = useCallback(() => {
|
|
298
|
+
const current = transientFocusLayoutStateRef.current;
|
|
299
|
+
if (!current) return;
|
|
300
|
+
activateTransientFocusState(current);
|
|
301
|
+
}, [activateTransientFocusState]);
|
|
302
|
+
const deactivateTransientFocusLayout = useCallback(() => {
|
|
303
|
+
const current = transientFocusLayoutStateRef.current;
|
|
304
|
+
if (!current || !current.active) return;
|
|
305
|
+
closePaneMenu();
|
|
306
|
+
setTransientFocusLayout({ ...current, active: false });
|
|
307
|
+
}, [closePaneMenu, setTransientFocusLayout]);
|
|
308
|
+
const exitTransientFocusLayout = useCallback(() => {
|
|
309
|
+
closePaneMenu();
|
|
310
|
+
setTransientFocusLayout(null);
|
|
311
|
+
}, [closePaneMenu, setTransientFocusLayout]);
|
|
312
|
+
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
setTransientLayout(
|
|
315
|
+
transientFocusLayoutState
|
|
316
|
+
? {
|
|
317
|
+
id: "pane-focus",
|
|
318
|
+
label: "^F Focus",
|
|
319
|
+
active: transientFocusActive,
|
|
320
|
+
onActivate: activateTransientFocusLayout,
|
|
321
|
+
onDeactivate: deactivateTransientFocusLayout,
|
|
322
|
+
onExit: exitTransientFocusLayout,
|
|
323
|
+
}
|
|
324
|
+
: null,
|
|
325
|
+
);
|
|
326
|
+
return () => setTransientLayout(null);
|
|
327
|
+
}, [
|
|
328
|
+
activateTransientFocusLayout,
|
|
329
|
+
deactivateTransientFocusLayout,
|
|
330
|
+
exitTransientFocusLayout,
|
|
331
|
+
setTransientLayout,
|
|
332
|
+
transientFocusActive,
|
|
333
|
+
transientFocusLayoutState,
|
|
334
|
+
]);
|
|
214
335
|
|
|
215
336
|
useShellPaneManagementShortcuts({
|
|
216
337
|
cancelActiveDrag,
|
|
@@ -226,6 +347,7 @@ export function Shell({
|
|
|
226
347
|
overlayOpen,
|
|
227
348
|
popOutFocusedPane,
|
|
228
349
|
startWindowMode,
|
|
350
|
+
toggleFocusedPaneFullscreen,
|
|
229
351
|
toggleFocusedPaneFloating,
|
|
230
352
|
});
|
|
231
353
|
|
|
@@ -333,6 +455,7 @@ export function Shell({
|
|
|
333
455
|
closePaneMenu,
|
|
334
456
|
contentHeight,
|
|
335
457
|
dispatch,
|
|
458
|
+
dockGeometryOptions,
|
|
336
459
|
dockDividerLayouts,
|
|
337
460
|
dockLeafLayouts,
|
|
338
461
|
dragRuntime,
|
|
@@ -349,6 +472,7 @@ export function Shell({
|
|
|
349
472
|
setHoveredMenuItemId,
|
|
350
473
|
setMenuState,
|
|
351
474
|
snapGuides,
|
|
475
|
+
transientFocusActive,
|
|
352
476
|
updateWindowModePreviewLayout,
|
|
353
477
|
visibleFloatingPanes,
|
|
354
478
|
visibleLayout,
|
|
@@ -409,6 +533,8 @@ export function Shell({
|
|
|
409
533
|
startNativeDockedDrag={startNativeDockedDrag}
|
|
410
534
|
startNativeFloatingDrag={startNativeFloatingDrag}
|
|
411
535
|
startNativeFloatResize={startNativeFloatResize}
|
|
536
|
+
transientFocusActive={transientFocusActive}
|
|
537
|
+
transientFocusPaneId={transientFocusPaneId}
|
|
412
538
|
visibleFloatingPanes={visibleFloatingPanes}
|
|
413
539
|
width={width}
|
|
414
540
|
windowModeDockResizePathKey={windowModeDockResizePathKey}
|
|
@@ -20,6 +20,7 @@ interface UseShellNativePointerRuntimeOptions {
|
|
|
20
20
|
selectWindowModePane: (paneId: string) => void;
|
|
21
21
|
setHoveredMenuItemId: Dispatch<SetStateAction<string | null>>;
|
|
22
22
|
setMenuState: Dispatch<SetStateAction<ActionMenuState | null>>;
|
|
23
|
+
transientFocusActive: boolean;
|
|
23
24
|
windowMode: WindowEditState | null;
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -35,6 +36,7 @@ export function useShellNativePointerRuntime({
|
|
|
35
36
|
selectWindowModePane,
|
|
36
37
|
setHoveredMenuItemId,
|
|
37
38
|
setMenuState,
|
|
39
|
+
transientFocusActive,
|
|
38
40
|
windowMode,
|
|
39
41
|
}: UseShellNativePointerRuntimeOptions) {
|
|
40
42
|
const {
|
|
@@ -69,6 +71,7 @@ export function useShellNativePointerRuntime({
|
|
|
69
71
|
const startNativeFloatingDrag = useCallback((paneId: string, rect: FloatingRect, event: ShellMouseEvent) => {
|
|
70
72
|
if (!nativePaneChrome) return;
|
|
71
73
|
if (windowMode) return;
|
|
74
|
+
if (transientFocusActive) return;
|
|
72
75
|
if (event.button === 2) return;
|
|
73
76
|
|
|
74
77
|
const pointer = getShellPointer(event);
|
|
@@ -83,11 +86,12 @@ export function useShellNativePointerRuntime({
|
|
|
83
86
|
};
|
|
84
87
|
updateDragFloatingRect({ paneId, rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } });
|
|
85
88
|
event.preventDefault();
|
|
86
|
-
}, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, updateDragFloatingRect, windowMode]);
|
|
89
|
+
}, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, updateDragFloatingRect, windowMode]);
|
|
87
90
|
|
|
88
91
|
const startNativeDockedDrag = useCallback((paneId: string, rect: LayoutBounds, event: ShellMouseEvent) => {
|
|
89
92
|
if (!nativePaneChrome) return;
|
|
90
93
|
if (windowMode) return;
|
|
94
|
+
if (transientFocusActive) return;
|
|
91
95
|
if (event.button === 2) return;
|
|
92
96
|
|
|
93
97
|
const pointer = getShellPointer(event);
|
|
@@ -101,10 +105,11 @@ export function useShellNativePointerRuntime({
|
|
|
101
105
|
origRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
102
106
|
};
|
|
103
107
|
event.preventDefault();
|
|
104
|
-
}, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, windowMode]);
|
|
108
|
+
}, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, windowMode]);
|
|
105
109
|
|
|
106
110
|
const startNativeFloatResize = useCallback((paneId: string, rect: FloatingRect, event: ShellMouseEvent) => {
|
|
107
111
|
if (!nativePaneChrome) return;
|
|
112
|
+
if (transientFocusActive) return;
|
|
108
113
|
|
|
109
114
|
const pointer = getShellPointer(event);
|
|
110
115
|
if (windowMode) {
|
|
@@ -122,10 +127,11 @@ export function useShellNativePointerRuntime({
|
|
|
122
127
|
updateDragFloatingRect({ paneId, rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } });
|
|
123
128
|
event.stopPropagation();
|
|
124
129
|
event.preventDefault();
|
|
125
|
-
}, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, selectWindowModePane, updateDragFloatingRect, windowMode]);
|
|
130
|
+
}, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, selectWindowModePane, transientFocusActive, updateDragFloatingRect, windowMode]);
|
|
126
131
|
|
|
127
132
|
const startNativeDividerDrag = useCallback((divider: DockDividerLayout, event: ShellMouseEvent) => {
|
|
128
133
|
if (!nativePaneChrome) return;
|
|
134
|
+
if (transientFocusActive) return;
|
|
129
135
|
|
|
130
136
|
const pointer = getShellPointer(event);
|
|
131
137
|
if (menuState) {
|
|
@@ -148,7 +154,7 @@ export function useShellNativePointerRuntime({
|
|
|
148
154
|
});
|
|
149
155
|
event.stopPropagation();
|
|
150
156
|
event.preventDefault();
|
|
151
|
-
}, [dragRef, getShellPointer, menuState, nativePaneChrome, setHoveredMenuItemId, setMenuState, updateDividerPreview]);
|
|
157
|
+
}, [dragRef, getShellPointer, menuState, nativePaneChrome, setHoveredMenuItemId, setMenuState, transientFocusActive, updateDividerPreview]);
|
|
152
158
|
|
|
153
159
|
const handlePaneAction = useCallback((paneId: string, rect: LayoutBounds, event: ShellMouseEvent) => {
|
|
154
160
|
if (windowMode) return;
|
|
@@ -48,6 +48,8 @@ interface ShellPaneLayersProps {
|
|
|
48
48
|
startNativeDockedDrag: (paneId: string, rect: LayoutBounds, event: any) => void;
|
|
49
49
|
startNativeFloatingDrag: (paneId: string, rect: FloatingRect, event: any) => void;
|
|
50
50
|
startNativeFloatResize: (paneId: string, rect: FloatingRect, event: any) => void;
|
|
51
|
+
transientFocusActive: boolean;
|
|
52
|
+
transientFocusPaneId: string | null;
|
|
51
53
|
visibleFloatingPanes: VisibleFloatingPane[];
|
|
52
54
|
width: number;
|
|
53
55
|
windowModeDockResizePathKey: string | null;
|
|
@@ -79,6 +81,8 @@ export function ShellPaneLayers({
|
|
|
79
81
|
startNativeDockedDrag,
|
|
80
82
|
startNativeFloatingDrag,
|
|
81
83
|
startNativeFloatResize,
|
|
84
|
+
transientFocusActive,
|
|
85
|
+
transientFocusPaneId,
|
|
82
86
|
visibleFloatingPanes,
|
|
83
87
|
width,
|
|
84
88
|
windowModeDockResizePathKey,
|
|
@@ -87,8 +91,12 @@ export function ShellPaneLayers({
|
|
|
87
91
|
return (
|
|
88
92
|
<>
|
|
89
93
|
{dockLeafLayouts.map((leaf) => {
|
|
94
|
+
if (transientFocusActive && leaf.instanceId !== transientFocusPaneId) return null;
|
|
90
95
|
const pane = paneMap.get(leaf.instanceId);
|
|
91
96
|
if (!pane) return null;
|
|
97
|
+
const rect = transientFocusActive
|
|
98
|
+
? { x: 0, y: 0, width, height: contentHeight }
|
|
99
|
+
: leaf.rect;
|
|
92
100
|
const focused = focusedPaneId === leaf.instanceId && (!overlayOpen || menuPaneId === leaf.instanceId);
|
|
93
101
|
const windowModeSelected = windowModePaneId === leaf.instanceId;
|
|
94
102
|
const showActions = focused || hoveredPaneId === leaf.instanceId || menuPaneId === leaf.instanceId;
|
|
@@ -96,17 +104,17 @@ export function ShellPaneLayers({
|
|
|
96
104
|
<Box
|
|
97
105
|
key={`dock:${leaf.instanceId}`}
|
|
98
106
|
position="absolute"
|
|
99
|
-
left={
|
|
100
|
-
top={
|
|
101
|
-
width={
|
|
102
|
-
height={
|
|
107
|
+
left={rect.x}
|
|
108
|
+
top={rect.y}
|
|
109
|
+
width={rect.width}
|
|
110
|
+
height={rect.height}
|
|
103
111
|
>
|
|
104
112
|
<PaneFooterProvider>
|
|
105
113
|
{(footer) => {
|
|
106
114
|
const reserveFooter = shouldReservePaneFooter(nativePaneChrome, hasPaneFooterContent(footer));
|
|
107
115
|
const bodyFrame = resolvePaneBodyFrame({
|
|
108
|
-
width:
|
|
109
|
-
height:
|
|
116
|
+
width: rect.width,
|
|
117
|
+
height: rect.height,
|
|
110
118
|
nativePaneChrome,
|
|
111
119
|
reserveFooter,
|
|
112
120
|
});
|
|
@@ -115,18 +123,18 @@ export function ShellPaneLayers({
|
|
|
115
123
|
paneId={leaf.instanceId}
|
|
116
124
|
title={getPaneTitle(pane)}
|
|
117
125
|
focused={focused}
|
|
118
|
-
width={
|
|
119
|
-
height={
|
|
126
|
+
width={rect.width}
|
|
127
|
+
height={rect.height}
|
|
120
128
|
showActions={showActions}
|
|
121
129
|
windowModeSelected={windowModeSelected}
|
|
122
130
|
footer={footer}
|
|
123
131
|
onMouseDown={nativePaneChrome ? (event) => handleNativePaneMouseDown(leaf.instanceId, event) : undefined}
|
|
124
132
|
onMouseMove={() => setHoveredPaneIfChanged(leaf.instanceId)}
|
|
125
|
-
onHeaderMouseDown={nativePaneChrome ? (event) => startNativeDockedDrag(leaf.instanceId,
|
|
126
|
-
onHeaderMouseDrag={nativePaneChrome ? handleNativeDrag : undefined}
|
|
127
|
-
onHeaderMouseDragEnd={nativePaneChrome ? handleNativeDrag : undefined}
|
|
128
|
-
onHeaderContextMenu={nativePaneChrome && nativeContextMenu === true ? (event) => handleNativePaneContextMenu(leaf.instanceId,
|
|
129
|
-
onActionMouseDown={(event) => handlePaneAction(leaf.instanceId,
|
|
133
|
+
onHeaderMouseDown={nativePaneChrome && !transientFocusActive ? (event) => startNativeDockedDrag(leaf.instanceId, rect, event) : undefined}
|
|
134
|
+
onHeaderMouseDrag={nativePaneChrome && !transientFocusActive ? handleNativeDrag : undefined}
|
|
135
|
+
onHeaderMouseDragEnd={nativePaneChrome && !transientFocusActive ? handleNativeDrag : undefined}
|
|
136
|
+
onHeaderContextMenu={nativePaneChrome && nativeContextMenu === true ? (event) => handleNativePaneContextMenu(leaf.instanceId, rect, event) : undefined}
|
|
137
|
+
onActionMouseDown={(event) => handlePaneAction(leaf.instanceId, rect, event)}
|
|
130
138
|
>
|
|
131
139
|
<PaneContent
|
|
132
140
|
component={pane.def.component}
|
|
@@ -145,7 +153,10 @@ export function ShellPaneLayers({
|
|
|
145
153
|
})}
|
|
146
154
|
|
|
147
155
|
{visibleFloatingPanes.map(({ pane, rect }) => {
|
|
148
|
-
|
|
156
|
+
if (transientFocusActive && pane.instance.instanceId !== transientFocusPaneId) return null;
|
|
157
|
+
const preview = transientFocusActive
|
|
158
|
+
? { x: 0, y: 0, width, height: contentHeight }
|
|
159
|
+
: dragFloatingRect?.paneId === pane.instance.instanceId
|
|
149
160
|
? constrainFloatingRectToBounds(dragFloatingRect.rect, width, contentHeight)
|
|
150
161
|
: rect;
|
|
151
162
|
const focused = focusedPaneId === pane.instance.instanceId && (!overlayOpen || menuPaneId === pane.instance.instanceId);
|
|
@@ -203,6 +214,7 @@ export function ShellPaneLayers({
|
|
|
203
214
|
})}
|
|
204
215
|
|
|
205
216
|
{dockDividerLayouts.map((divider) => {
|
|
217
|
+
if (transientFocusActive) return null;
|
|
206
218
|
const dividerPathKey = pathKey(divider.path);
|
|
207
219
|
const previewActive = dividerPreview?.pathKey === dividerPathKey;
|
|
208
220
|
const active = previewActive || windowModeDockResizePathKey === dividerPathKey;
|
|
@@ -25,6 +25,7 @@ interface ShellPaneManagementShortcutOptions {
|
|
|
25
25
|
overlayOpen: boolean;
|
|
26
26
|
popOutFocusedPane(): boolean;
|
|
27
27
|
startWindowMode(paneId?: string, mode?: WindowEditMode): void;
|
|
28
|
+
toggleFocusedPaneFullscreen(): boolean;
|
|
28
29
|
toggleFocusedPaneFloating(): boolean;
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -42,6 +43,7 @@ export function useShellPaneManagementShortcuts({
|
|
|
42
43
|
overlayOpen,
|
|
43
44
|
popOutFocusedPane,
|
|
44
45
|
startWindowMode,
|
|
46
|
+
toggleFocusedPaneFullscreen,
|
|
45
47
|
toggleFocusedPaneFloating,
|
|
46
48
|
}: ShellPaneManagementShortcutOptions): void {
|
|
47
49
|
const doubleEscapeCloseRef = useRef(createDoubleEscapeCloseState());
|
|
@@ -53,6 +55,20 @@ export function useShellPaneManagementShortcuts({
|
|
|
53
55
|
}, [overlayOpen]);
|
|
54
56
|
|
|
55
57
|
useShortcut((event) => {
|
|
58
|
+
const shortcut = resolvePaneManagementShortcut(event);
|
|
59
|
+
if (shortcut === "toggle-fullscreen" && !overlayOpen) {
|
|
60
|
+
if (!inputCaptured || inputCaptureAllowsPaneManagementShortcut(shortcut, event)) {
|
|
61
|
+
if (hasActiveDrag()) {
|
|
62
|
+
cancelActiveDrag();
|
|
63
|
+
}
|
|
64
|
+
if (toggleFocusedPaneFullscreen()) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
event.stopPropagation();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
const isEscape = event.name === "escape" || event.name === "esc";
|
|
57
73
|
if (isEscape) {
|
|
58
74
|
const doubleEscapeState = doubleEscapeCloseRef.current;
|
|
@@ -91,6 +107,9 @@ export function useShellPaneManagementShortcuts({
|
|
|
91
107
|
case "settings":
|
|
92
108
|
handled = openFocusedPaneSettings();
|
|
93
109
|
break;
|
|
110
|
+
case "toggle-fullscreen":
|
|
111
|
+
handled = toggleFocusedPaneFullscreen();
|
|
112
|
+
break;
|
|
94
113
|
case "toggle-floating":
|
|
95
114
|
handled = toggleFocusedPaneFloating();
|
|
96
115
|
break;
|
|
@@ -2,6 +2,7 @@ import type { KeyEventLike } from "../../../react/input";
|
|
|
2
2
|
|
|
3
3
|
export const PANE_MANAGEMENT_ACCELERATORS = {
|
|
4
4
|
settings: "CmdOrCtrl+,",
|
|
5
|
+
fullscreen: "CmdOrCtrl+Shift+F",
|
|
5
6
|
toggleFloating: "CmdOrCtrl+Shift+D",
|
|
6
7
|
popOut: "CmdOrCtrl+Shift+O",
|
|
7
8
|
copyScreenshot: "CmdOrCtrl+Shift+C",
|
|
@@ -15,6 +16,7 @@ export const PANE_MANAGEMENT_ACCELERATORS = {
|
|
|
15
16
|
|
|
16
17
|
export type PaneManagementShortcut =
|
|
17
18
|
| "settings"
|
|
19
|
+
| "toggle-fullscreen"
|
|
18
20
|
| "toggle-floating"
|
|
19
21
|
| "pop-out"
|
|
20
22
|
| "copy-screenshot"
|
|
@@ -38,6 +40,7 @@ export function resolvePaneManagementShortcut(
|
|
|
38
40
|
if (!shifted || event.alt) return null;
|
|
39
41
|
if (name === "c") return "copy-screenshot";
|
|
40
42
|
if (name === "d") return "toggle-floating";
|
|
43
|
+
if (name === "f") return "toggle-fullscreen";
|
|
41
44
|
if (name === "o") return "pop-out";
|
|
42
45
|
if (name === "l") return "layout-actions";
|
|
43
46
|
if (name === "g") return "gridlock-all";
|
|
@@ -50,6 +53,7 @@ export function inputCaptureAllowsPaneManagementShortcut(
|
|
|
50
53
|
shortcut: PaneManagementShortcut,
|
|
51
54
|
event: Pick<KeyEventLike, "meta" | "super" | "targetEditable">,
|
|
52
55
|
): boolean {
|
|
56
|
+
if (shortcut === "toggle-fullscreen") return event.meta || event.super === true;
|
|
53
57
|
if (shortcut !== "close" && shortcut !== "close-all-floating") return false;
|
|
54
58
|
return event.meta || event.super || event.targetEditable !== true;
|
|
55
59
|
}
|
|
@@ -41,6 +41,7 @@ interface UseShellTerminalPointerRuntimeOptions {
|
|
|
41
41
|
selectWindowModePane: (paneId: string) => void;
|
|
42
42
|
setHoveredMenuItemId: Dispatch<SetStateAction<string | null>>;
|
|
43
43
|
setMenuState: Dispatch<SetStateAction<ActionMenuState | null>>;
|
|
44
|
+
transientFocusActive: boolean;
|
|
44
45
|
visibleFloatingPanes: VisibleFloatingPane[];
|
|
45
46
|
width: number;
|
|
46
47
|
windowMode: WindowEditState | null;
|
|
@@ -79,6 +80,7 @@ export function useShellTerminalPointerRuntime({
|
|
|
79
80
|
selectWindowModePane,
|
|
80
81
|
setHoveredMenuItemId,
|
|
81
82
|
setMenuState,
|
|
83
|
+
transientFocusActive,
|
|
82
84
|
visibleFloatingPanes,
|
|
83
85
|
width,
|
|
84
86
|
windowMode,
|
|
@@ -132,25 +134,27 @@ export function useShellTerminalPointerRuntime({
|
|
|
132
134
|
return;
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
137
|
+
if (!transientFocusActive) {
|
|
138
|
+
for (const divider of dockDividerLayouts) {
|
|
139
|
+
if (!pointInRect(divider.rect, event.x, shellY)) continue;
|
|
140
|
+
dragRef.current = {
|
|
141
|
+
type: "divider",
|
|
142
|
+
path: divider.path,
|
|
143
|
+
axis: divider.axis,
|
|
144
|
+
startX: preciseX,
|
|
145
|
+
startY: preciseShellY,
|
|
146
|
+
startRatio: divider.ratio,
|
|
147
|
+
bounds: divider.bounds,
|
|
148
|
+
};
|
|
149
|
+
updateDividerPreview({
|
|
150
|
+
pathKey: divider.path.join("."),
|
|
151
|
+
rect: divider.rect,
|
|
152
|
+
ratio: divider.ratio,
|
|
153
|
+
});
|
|
154
|
+
event.stopPropagation();
|
|
155
|
+
event.preventDefault();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
for (const leaf of dockLeafLayouts) {
|
|
@@ -279,6 +283,11 @@ export function useShellTerminalPointerRuntime({
|
|
|
279
283
|
event.preventDefault();
|
|
280
284
|
return;
|
|
281
285
|
}
|
|
286
|
+
if (relativeY === 0 && transientFocusActive) {
|
|
287
|
+
event.stopPropagation();
|
|
288
|
+
event.preventDefault();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
282
291
|
if (relativeY === 0) {
|
|
283
292
|
dragRef.current = {
|
|
284
293
|
type: "pane-drag",
|
|
@@ -317,6 +326,7 @@ export function useShellTerminalPointerRuntime({
|
|
|
317
326
|
selectWindowModePane,
|
|
318
327
|
setHoveredMenuItemId,
|
|
319
328
|
setMenuState,
|
|
329
|
+
transientFocusActive,
|
|
320
330
|
updateDividerPreview,
|
|
321
331
|
updateDragFloatingRect,
|
|
322
332
|
visibleFloatingPanes,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
applyWindowEditDirection,
|
|
14
14
|
cycleWindowEditFocus,
|
|
15
15
|
cycleWindowEditPane,
|
|
16
|
+
cycleWindowEditTarget,
|
|
16
17
|
directionFromWindowEditKey,
|
|
17
18
|
getWindowEditPaneIds,
|
|
18
19
|
normalizeWindowEditFocus,
|
|
@@ -72,7 +73,10 @@ export function useShellWindowMode({
|
|
|
72
73
|
);
|
|
73
74
|
const windowModePaneId = windowMode?.paneId;
|
|
74
75
|
const activeLayout = windowMode?.previewLayout ?? visibleLayout;
|
|
75
|
-
const windowModePaneIds = useMemo(
|
|
76
|
+
const windowModePaneIds = useMemo(
|
|
77
|
+
() => getWindowEditPaneIds(activeLayout, bounds, dockGeometryOptions),
|
|
78
|
+
[activeLayout, bounds, dockGeometryOptions],
|
|
79
|
+
);
|
|
76
80
|
const windowModeDockMovePreview = useMemo(
|
|
77
81
|
() => resolveWindowEditDockMovePreview(windowMode, bounds, dockGeometryOptions),
|
|
78
82
|
[bounds, dockGeometryOptions, windowMode],
|
|
@@ -210,12 +214,16 @@ export function useShellWindowMode({
|
|
|
210
214
|
}
|
|
211
215
|
} else if (name === "w") {
|
|
212
216
|
setWindowMode((current) => current
|
|
213
|
-
?
|
|
217
|
+
? current.mode === "move" && current.focus.kind === "dock-move"
|
|
218
|
+
? cycleWindowEditTarget(current, bounds, dockGeometryOptions, event.shift ? -1 : 1)
|
|
219
|
+
: cycleWindowEditPane(current, windowModePaneIds, bounds, dockGeometryOptions, event.shift ? -1 : 1)
|
|
214
220
|
: current);
|
|
215
221
|
} else if (name === "tab") {
|
|
216
222
|
setWindowMode((current) => current
|
|
217
223
|
? current.mode === "move"
|
|
218
|
-
?
|
|
224
|
+
? current.focus.kind === "dock-move"
|
|
225
|
+
? cycleWindowEditTarget(current, bounds, dockGeometryOptions, event.shift ? -1 : 1)
|
|
226
|
+
: cycleWindowEditPane(current, windowModePaneIds, bounds, dockGeometryOptions, event.shift ? -1 : 1)
|
|
219
227
|
: {
|
|
220
228
|
...current,
|
|
221
229
|
focus: cycleWindowEditFocus(
|
|
@@ -40,6 +40,13 @@ interface ShellWindowModeOverlaysProps {
|
|
|
40
40
|
windowModeDockMovePreview: WindowEditDockMovePreview | null;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function getHighlightedPaneId(windowMode: WindowEditState | null, focusedPaneId: string | null): string | null {
|
|
44
|
+
if (windowMode?.mode === "move" && windowMode.focus.kind === "dock-move") {
|
|
45
|
+
return windowMode.focus.targetId;
|
|
46
|
+
}
|
|
47
|
+
return windowMode?.paneId ?? focusedPaneId;
|
|
48
|
+
}
|
|
49
|
+
|
|
43
50
|
function windowModeBannerKey(windowMode: WindowEditState): string {
|
|
44
51
|
const focusKey = windowMode.focus.kind === "dock-move"
|
|
45
52
|
? `${windowMode.focus.targetId}:${windowMode.focus.position}`
|
|
@@ -69,7 +76,7 @@ export function ShellWindowModeOverlays({
|
|
|
69
76
|
windowMode,
|
|
70
77
|
windowModeDockMovePreview,
|
|
71
78
|
}: ShellWindowModeOverlaysProps) {
|
|
72
|
-
const highlightedPaneId = windowMode
|
|
79
|
+
const highlightedPaneId = getHighlightedPaneId(windowMode, focusedPaneId);
|
|
73
80
|
const highlightedFloatingPane = highlightedPaneId
|
|
74
81
|
? visibleFloatingPanes.find((entry) => entry.pane.instance.instanceId === highlightedPaneId)
|
|
75
82
|
: null;
|