gloomberb 0.9.2 → 0.9.4
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 +24 -2
- package/bin/gloomberb +1 -1
- package/package.json +1 -1
- package/src/app/runtime/desktop-menu.ts +5 -1
- package/src/app/runtime/ticker-refresh.ts +4 -1
- package/src/app.tsx +20 -8
- package/src/brokers/account-cache.ts +1 -1
- package/src/brokers/sync-broker-instance.test.ts +107 -0
- package/src/brokers/sync-broker-instance.ts +22 -10
- 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/desktop-pane-shot.ts +2 -1
- 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/pane-functions/screenshot.ts +13 -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/bar-chart-renderer.test.ts +108 -3
- package/src/components/chart/bar-chart-renderer.ts +226 -27
- 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 +34 -6
- package/src/components/chart/price-axis-labels.tsx +3 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
- package/src/components/chart/static/bar-chart-surface.tsx +151 -28
- package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
- package/src/components/chart/static/chart/surface.test.tsx +92 -0
- package/src/components/chart/static/chart/surface.tsx +167 -24
- package/src/components/chart/stock-chart/index.test.ts +25 -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/layout-items/current-layout.ts +5 -1
- 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/detached-pane-shell.tsx +6 -3
- package/src/components/layout/floating-pane.tsx +3 -2
- package/src/components/layout/pane/content.tsx +20 -8
- package/src/components/layout/pane/footer/registration.tsx +6 -2
- package/src/components/layout/pane/frame.tsx +4 -4
- package/src/components/layout/pane/header.tsx +40 -4
- package/src/components/layout/pane/index.tsx +3 -1
- package/src/components/layout/pane/sizing.test.ts +43 -0
- package/src/components/layout/pane/sizing.ts +13 -4
- package/src/components/layout/shell/active-drag.ts +5 -1
- package/src/components/layout/shell/cursor-occlusion.ts +196 -0
- package/src/components/layout/shell/drag/runtime.ts +28 -3
- package/src/components/layout/shell/fullscreen.ts +10 -0
- package/src/components/layout/shell/index.test.tsx +487 -4
- package/src/components/layout/shell/index.tsx +165 -4
- package/src/components/layout/shell/layout-state.test.ts +33 -0
- package/src/components/layout/shell/layout-state.ts +4 -9
- package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
- package/src/components/layout/shell/pane/layers.tsx +34 -16
- 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/visible-layout.ts +17 -0
- 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 +111 -1
- package/src/components/layout/status-bar.tsx +37 -7
- 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/onboarding/onboarding-steps.tsx +8 -28
- package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
- package/src/components/onboarding/onboarding-wizard.tsx +2 -4
- package/src/components/onboarding/portfolio-step/index.tsx +0 -12
- package/src/components/onboarding/portfolio-step/types.ts +1 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -5
- package/src/components/price-performance.tsx +9 -1
- package/src/components/table-view-shared.tsx +12 -4
- package/src/components/ui/data-table/opentui/index.tsx +25 -12
- package/src/components/ui/data-table/types.ts +4 -0
- package/src/components/ui/fields.tsx +10 -3
- package/src/components/ui/page-stack-view.tsx +6 -2
- package/src/components/ui/table-layout.test.ts +25 -0
- package/src/components/ui/table-layout.ts +22 -6
- package/src/components/ui/tabs.tsx +26 -10
- package/src/components/ui/ui.test.tsx +87 -0
- package/src/core/app-services.ts +7 -4
- package/src/core/state/app/layout-reducer.ts +2 -2
- package/src/data/config/store/index.ts +1 -2
- package/src/plugins/builtin/analytics/index.test.tsx +6 -2
- package/src/plugins/builtin/analytics/pane-model.ts +43 -8
- 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/kelly-sizer/constants.ts +3 -0
- package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
- package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
- package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
- package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
- package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
- package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
- package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
- package/src/plugins/builtin/layout-manager/index.tsx +5 -1
- package/src/plugins/builtin/news/wire/read-state.ts +9 -32
- package/src/plugins/builtin/options/view.test.tsx +141 -4
- package/src/plugins/builtin/options/view.tsx +76 -15
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
- 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/index.test.ts +23 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +3 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
- package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
- 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/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
- package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/ibkr/broker-adapter.ts +19 -0
- package/src/plugins/ibkr/config.test.ts +31 -2
- package/src/plugins/ibkr/config.ts +9 -2
- package/src/plugins/ibkr/flex/index.test.ts +18 -9
- package/src/plugins/ibkr/flex/index.ts +30 -0
- package/src/plugins/ibkr/flex/statement-client.ts +68 -14
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
- package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
- package/src/plugins/pane-manager/docking.ts +9 -2
- package/src/plugins/pane-manager/gridlock.ts +13 -5
- package/src/plugins/pane-manager/layout-state.ts +25 -0
- package/src/plugins/pane-manager/queries.ts +8 -2
- package/src/plugins/pane-manager.test.ts +33 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +10 -4
- 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/detail/book.tsx +141 -96
- package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
- package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
- package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
- package/src/plugins/prediction-markets/detail.test.tsx +50 -0
- package/src/plugins/prediction-markets/pane.test.tsx +75 -0
- package/src/plugins/prediction-markets/pane.tsx +71 -23
- package/src/plugins/prediction-markets/test-helpers.tsx +42 -23
- 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/cli-pane-shot-entry.tsx +13 -2
- package/src/renderers/electrobun/view/data-table/index.tsx +19 -4
- package/src/renderers/electrobun/view/data-table/row.tsx +30 -6
- package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
- package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
- package/src/renderers/electrobun/view/host/input.tsx +115 -18
- package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
- 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/styles.css +11 -0
- package/src/renderers/electrobun/view/ui-host.tsx +1 -1
- package/src/renderers/opentui/host.tsx +17 -1
- package/src/renderers/opentui/start.tsx +13 -5
- package/src/renderers/opentui/ui-host.tsx +57 -25
- package/src/sources/provider-router/batches.ts +16 -2
- package/src/sources/provider-router/financial-routes.ts +5 -0
- package/src/sources/provider-router/financials.ts +11 -23
- package/src/sources/provider-router/index.test.ts +189 -0
- package/src/sources/provider-router/primary.ts +3 -2
- package/src/sources/sec-edgar.test.ts +83 -0
- package/src/sources/sec-edgar.ts +231 -0
- package/src/sources/yahoo-finance.test.ts +51 -0
- package/src/sources/yahoo-finance.ts +49 -1
- package/src/state/app/context/index.test.tsx +5 -8
- package/src/state/app/input-capture.ts +12 -0
- package/src/types/broker.ts +1 -0
- package/src/types/config.ts +10 -0
- package/src/types/plugin.ts +28 -0
- package/src/types/trading.ts +2 -0
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +6 -0
- package/src/utils/financial-statements.ts +23 -0
- package/src/utils/search-focus-navigation.ts +24 -0
- package/src/version.ts +1 -1
- package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +0 -62
|
@@ -67,10 +67,10 @@ interface UseStockChartViewportRuntimeOptions {
|
|
|
67
67
|
persistChartControls: (range: TimeRange, resolution: ChartResolution) => void;
|
|
68
68
|
renderedResolution: ChartResolution;
|
|
69
69
|
selectionSupportMap: ReadonlyMap<ManualChartResolution, TimeRange>;
|
|
70
|
+
persistChartRenderMode: (mode: ChartRenderMode) => void;
|
|
70
71
|
setPendingAutoWindowOverride: Dispatch<SetStateAction<DateWindowRange | null>>;
|
|
71
72
|
setRenderedAutoView: Dispatch<SetStateAction<AutoRenderedView | null>>;
|
|
72
73
|
setRequestedResolution: Dispatch<SetStateAction<ChartResolution>>;
|
|
73
|
-
setStoredRenderMode: Dispatch<SetStateAction<ChartRenderMode>>;
|
|
74
74
|
setViewState: Dispatch<SetStateAction<StockChartViewportState>>;
|
|
75
75
|
storedRangePreset: TimeRange;
|
|
76
76
|
storedRenderMode: ChartRenderMode;
|
|
@@ -101,12 +101,12 @@ export function useStockChartViewportRuntime({
|
|
|
101
101
|
pendingCanonicalResetRef,
|
|
102
102
|
pendingExpansionRef,
|
|
103
103
|
persistChartControls,
|
|
104
|
+
persistChartRenderMode,
|
|
104
105
|
renderedResolution,
|
|
105
106
|
selectionSupportMap,
|
|
106
107
|
setPendingAutoWindowOverride,
|
|
107
108
|
setRenderedAutoView,
|
|
108
109
|
setRequestedResolution,
|
|
109
|
-
setStoredRenderMode,
|
|
110
110
|
setViewState,
|
|
111
111
|
storedRangePreset,
|
|
112
112
|
storedRenderMode,
|
|
@@ -172,10 +172,10 @@ export function useStockChartViewportRuntime({
|
|
|
172
172
|
});
|
|
173
173
|
|
|
174
174
|
const persistRenderMode = useCallback((nextMode: ChartRenderMode) => {
|
|
175
|
-
if (!compact
|
|
176
|
-
|
|
175
|
+
if (!compact) {
|
|
176
|
+
persistChartRenderMode(nextMode);
|
|
177
177
|
}
|
|
178
|
-
}, [compact,
|
|
178
|
+
}, [compact, persistChartRenderMode]);
|
|
179
179
|
|
|
180
180
|
const expandBufferRange = useCallback((action: PendingExpansionAction): boolean => {
|
|
181
181
|
if (compact) return false;
|
|
@@ -87,7 +87,11 @@ export function buildCurrentLayoutItems({
|
|
|
87
87
|
kind: "action",
|
|
88
88
|
action: () => {
|
|
89
89
|
const { width, height } = pluginRegistry.getTermSizeFn();
|
|
90
|
-
persistLayoutChange(gridlockAllPanes(
|
|
90
|
+
persistLayoutChange(gridlockAllPanes(
|
|
91
|
+
currentLayout,
|
|
92
|
+
{ x: 0, y: 0, width, height },
|
|
93
|
+
pluginRegistry.panes,
|
|
94
|
+
));
|
|
91
95
|
notifyGridlockRevert();
|
|
92
96
|
closeAll({ revertThemePreview: false });
|
|
93
97
|
},
|
|
@@ -16,7 +16,17 @@ import {
|
|
|
16
16
|
getPaneTemplateDisplayLabel,
|
|
17
17
|
} from "./items";
|
|
18
18
|
|
|
19
|
+
function paneTemplateHasConfigFields(template: PaneTemplateDef): boolean {
|
|
20
|
+
if (template.wizard?.some((step) => step.type !== "info")) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return !!buildGeneratedTemplateField(template, null).field;
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
export function shouldOpenPaneTemplateConfig(template: PaneTemplateDef, arg?: string): boolean {
|
|
27
|
+
if (!paneTemplateHasConfigFields(template)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
20
30
|
if (template.wizard && template.wizard.length > 0) {
|
|
21
31
|
if (!arg?.trim()) {
|
|
22
32
|
return true;
|
|
@@ -117,6 +117,42 @@ function registerOptionalTextPane(pluginRegistry: MutablePaneRegistry): void {
|
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
function registerQueryOnlyPane(pluginRegistry: MutablePaneRegistry): void {
|
|
121
|
+
mutableRegistryMap(pluginRegistry.panes).set("prediction-markets", {
|
|
122
|
+
id: "prediction-markets",
|
|
123
|
+
name: "Prediction Markets",
|
|
124
|
+
component: () => null,
|
|
125
|
+
defaultPosition: "left",
|
|
126
|
+
defaultMode: "floating",
|
|
127
|
+
});
|
|
128
|
+
mutableRegistryMap(pluginRegistry.paneTemplates).set("new-prediction-markets-pane", {
|
|
129
|
+
id: "new-prediction-markets-pane",
|
|
130
|
+
paneId: "prediction-markets",
|
|
131
|
+
label: "Prediction Markets",
|
|
132
|
+
description: "Open a new prediction markets browser pane",
|
|
133
|
+
keywords: ["prediction", "markets", "polymarket", "kalshi", "events"],
|
|
134
|
+
shortcut: { prefix: "PM", argPlaceholder: "query", argKind: "text" },
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function registerShortcutOnlyPane(pluginRegistry: MutablePaneRegistry): void {
|
|
139
|
+
mutableRegistryMap(pluginRegistry.panes).set("top-news", {
|
|
140
|
+
id: "top-news",
|
|
141
|
+
name: "Top News",
|
|
142
|
+
component: () => null,
|
|
143
|
+
defaultPosition: "right",
|
|
144
|
+
defaultMode: "floating",
|
|
145
|
+
});
|
|
146
|
+
mutableRegistryMap(pluginRegistry.paneTemplates).set("top-news-pane", {
|
|
147
|
+
id: "top-news-pane",
|
|
148
|
+
paneId: "top-news",
|
|
149
|
+
label: "Top News",
|
|
150
|
+
description: "Curated top market stories ranked by importance",
|
|
151
|
+
keywords: ["top", "news", "headlines", "stories"],
|
|
152
|
+
shortcut: { prefix: "TOP" },
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
120
156
|
describe("CommandBar pane and layout routes", () => {
|
|
121
157
|
const layoutModeConfig = (config: AppConfig): AppConfig => {
|
|
122
158
|
const research = cloneLayout(config.layout);
|
|
@@ -271,6 +307,46 @@ describe("CommandBar pane and layout routes", () => {
|
|
|
271
307
|
expect(testSetup.captureCharFrame()).not.toContain("Create Pane");
|
|
272
308
|
});
|
|
273
309
|
|
|
310
|
+
for (const scenario of [
|
|
311
|
+
{
|
|
312
|
+
query: "prediction markets",
|
|
313
|
+
register: registerQueryOnlyPane,
|
|
314
|
+
templateId: "new-prediction-markets-pane",
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
query: "top news",
|
|
318
|
+
register: registerShortcutOnlyPane,
|
|
319
|
+
templateId: "top-news-pane",
|
|
320
|
+
},
|
|
321
|
+
] as const) {
|
|
322
|
+
test(`creates ${scenario.templateId} directly when it has no config fields`, async () => {
|
|
323
|
+
const created: CreatedPaneCall[] = [];
|
|
324
|
+
|
|
325
|
+
testSetup = await testRender(<CommandBarHarness
|
|
326
|
+
query={scenario.query}
|
|
327
|
+
live
|
|
328
|
+
configurePluginRegistry={(pluginRegistry) => {
|
|
329
|
+
scenario.register(pluginRegistry);
|
|
330
|
+
recordPaneCreations(pluginRegistry, created);
|
|
331
|
+
}}
|
|
332
|
+
/>, {
|
|
333
|
+
width: 100,
|
|
334
|
+
height: 18,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
await testSetup.renderOnce();
|
|
338
|
+
|
|
339
|
+
await act(async () => {
|
|
340
|
+
testSetup!.mockInput.pressEnter();
|
|
341
|
+
await Bun.sleep(0);
|
|
342
|
+
await testSetup!.renderOnce();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(created).toEqual([{ templateId: scenario.templateId, options: undefined }]);
|
|
346
|
+
expect(testSetup.captureCharFrame()).not.toContain("Create Pane");
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
274
350
|
test("shows pane templates that share the same shortcut", async () => {
|
|
275
351
|
testSetup = await testRender(<CommandBarHarness
|
|
276
352
|
query="DUP"
|
|
@@ -26,6 +26,11 @@ export type DataTableKeyEvent = TableViewKeyEvent;
|
|
|
26
26
|
|
|
27
27
|
const DATA_TABLE_SELECTION_COMMIT_DELAY_MS = 150;
|
|
28
28
|
|
|
29
|
+
export interface DataTableRootKeyContext {
|
|
30
|
+
selectedIndex: number;
|
|
31
|
+
itemCount: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
export type DataTableSelectionChangeReason =
|
|
30
35
|
| "keyboard"
|
|
31
36
|
| "pointer"
|
|
@@ -95,7 +100,10 @@ export interface DataTableViewProps<
|
|
|
95
100
|
hoveredIdx?: number | null;
|
|
96
101
|
setHoveredIdx?: (index: number | null) => void;
|
|
97
102
|
keyboardNavigation?: boolean;
|
|
98
|
-
onRootKeyDown?: (
|
|
103
|
+
onRootKeyDown?: (
|
|
104
|
+
event: DataTableKeyEvent,
|
|
105
|
+
context: DataTableRootKeyContext,
|
|
106
|
+
) => boolean | void;
|
|
99
107
|
resetScrollKey?: unknown;
|
|
100
108
|
}
|
|
101
109
|
|
|
@@ -486,7 +494,10 @@ export function DataTableView<
|
|
|
486
494
|
if (event.defaultPrevented || event.propagationStopped) return;
|
|
487
495
|
if (!focused || !keyboardNavigation) return;
|
|
488
496
|
|
|
489
|
-
if (onRootKeyDown?.(event
|
|
497
|
+
if (onRootKeyDown?.(event, {
|
|
498
|
+
selectedIndex: effectiveSelectedIndexRef.current,
|
|
499
|
+
itemCount: tableProps.items.length,
|
|
500
|
+
})) return;
|
|
490
501
|
if (tableProps.items.length === 0) return;
|
|
491
502
|
|
|
492
503
|
if (isNextTableRowKey(event)) {
|
|
@@ -6,6 +6,7 @@ import { formatTimeAgo } from "../../utils/format";
|
|
|
6
6
|
import { isPlainKey } from "../../utils/keyboard";
|
|
7
7
|
import { toTimestampMillis } from "../../utils/timestamp";
|
|
8
8
|
import { DataTableStackView } from "../data-table/stack-view";
|
|
9
|
+
import type { DataTableRootKeyContext } from "../data-table/view";
|
|
9
10
|
import {
|
|
10
11
|
activeStackIndex,
|
|
11
12
|
sortIndexedStackRows,
|
|
@@ -45,9 +46,16 @@ interface FeedDataTableStackViewProps {
|
|
|
45
46
|
rootAfter?: ReactNode;
|
|
46
47
|
onRootKeyDown?: (event: {
|
|
47
48
|
name?: string;
|
|
49
|
+
sequence?: string;
|
|
50
|
+
ctrl?: boolean;
|
|
51
|
+
meta?: boolean;
|
|
52
|
+
super?: boolean;
|
|
53
|
+
alt?: boolean;
|
|
54
|
+
option?: boolean;
|
|
55
|
+
shift?: boolean;
|
|
48
56
|
preventDefault?: () => void;
|
|
49
57
|
stopPropagation?: () => void;
|
|
50
|
-
}) => boolean | void;
|
|
58
|
+
}, context: DataTableRootKeyContext) => boolean | void;
|
|
51
59
|
sourceLabel?: string;
|
|
52
60
|
titleLabel?: string;
|
|
53
61
|
emptyStateTitle?: string;
|
package/src/components/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type { TickerListVisibleRange } from "./ticker/list-table-view";
|
|
|
8
8
|
export { TickerBadgeList } from "./ticker/badge/list";
|
|
9
9
|
export { InputSearchBar } from "./input-search-bar";
|
|
10
10
|
export { DataTableView } from "./data-table/view";
|
|
11
|
-
export type { DataTableKeyEvent } from "./data-table/view";
|
|
11
|
+
export type { DataTableKeyEvent, DataTableRootKeyContext } from "./data-table/view";
|
|
12
12
|
export { DataTableStackView } from "./data-table/stack-view";
|
|
13
13
|
export { FeedDataTableStackView } from "./feed-data-table/stack-view";
|
|
14
14
|
export type { FeedDataTableItem } from "./feed-data-table/stack-view";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { act, useCallback, useRef, useState, type Dispatch, type SetStateAction } from "react";
|
|
3
|
+
import { testRender } from "../renderers/opentui/test-utils";
|
|
4
|
+
import { AppContext, createInitialState, type AppAction } from "../state/app/context";
|
|
5
|
+
import { createDefaultConfig } from "../types/config";
|
|
6
|
+
import type { InputRenderable } from "../ui";
|
|
7
|
+
import { InputSearchBar } from "./input-search-bar";
|
|
8
|
+
|
|
9
|
+
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
10
|
+
let setSearchActive: Dispatch<SetStateAction<boolean>> | null = null;
|
|
11
|
+
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
setSearchActive = null;
|
|
14
|
+
if (!testSetup) return;
|
|
15
|
+
await act(async () => {
|
|
16
|
+
testSetup!.renderer.destroy();
|
|
17
|
+
});
|
|
18
|
+
testSetup = undefined;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function Harness({
|
|
22
|
+
actions,
|
|
23
|
+
onNavigateDown,
|
|
24
|
+
}: {
|
|
25
|
+
actions: AppAction[];
|
|
26
|
+
onNavigateDown?: () => void;
|
|
27
|
+
}) {
|
|
28
|
+
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-input-search-bar"));
|
|
29
|
+
const inputRef = useRef<InputRenderable | null>(null);
|
|
30
|
+
const [active, setActive] = useState(true);
|
|
31
|
+
setSearchActive = setActive;
|
|
32
|
+
const dispatch = useCallback((action: AppAction) => {
|
|
33
|
+
actions.push(action);
|
|
34
|
+
}, [actions]);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<AppContext value={{ state, dispatch }}>
|
|
38
|
+
<InputSearchBar
|
|
39
|
+
value=""
|
|
40
|
+
focused
|
|
41
|
+
active={active}
|
|
42
|
+
width={30}
|
|
43
|
+
focusToken={0}
|
|
44
|
+
inputRef={inputRef}
|
|
45
|
+
placeholder="search"
|
|
46
|
+
debounceMs={100}
|
|
47
|
+
onNavigateDown={onNavigateDown}
|
|
48
|
+
onFocus={() => {}}
|
|
49
|
+
onBlur={() => {}}
|
|
50
|
+
onQueryChange={() => {}}
|
|
51
|
+
/>
|
|
52
|
+
</AppContext>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe("InputSearchBar", () => {
|
|
57
|
+
test("captures app input while the search input is active", async () => {
|
|
58
|
+
const actions: AppAction[] = [];
|
|
59
|
+
|
|
60
|
+
testSetup = await testRender(<Harness actions={actions} />, { width: 40, height: 4 });
|
|
61
|
+
await act(async () => {
|
|
62
|
+
await testSetup!.renderOnce();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(actions).toEqual([{ type: "SET_INPUT_CAPTURED", captured: true }]);
|
|
66
|
+
if (!setSearchActive) throw new Error("search setter was not registered");
|
|
67
|
+
|
|
68
|
+
await act(async () => {
|
|
69
|
+
setSearchActive(false);
|
|
70
|
+
await testSetup!.renderOnce();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(actions).toEqual([
|
|
74
|
+
{ type: "SET_INPUT_CAPTURED", captured: true },
|
|
75
|
+
{ type: "SET_INPUT_CAPTURED", captured: false },
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("moves from the active search input to the table with Down", async () => {
|
|
80
|
+
const actions: AppAction[] = [];
|
|
81
|
+
let navigatedDown = 0;
|
|
82
|
+
|
|
83
|
+
testSetup = await testRender(
|
|
84
|
+
<Harness
|
|
85
|
+
actions={actions}
|
|
86
|
+
onNavigateDown={() => {
|
|
87
|
+
navigatedDown += 1;
|
|
88
|
+
}}
|
|
89
|
+
/>,
|
|
90
|
+
{ width: 40, height: 4 },
|
|
91
|
+
);
|
|
92
|
+
await act(async () => {
|
|
93
|
+
await testSetup!.renderOnce();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await act(async () => {
|
|
97
|
+
testSetup!.mockInput.pressArrow("down");
|
|
98
|
+
await testSetup!.renderOnce();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(navigatedDown).toBe(1);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState, type RefObject } from "react";
|
|
2
2
|
import { Box, Input, Text, type InputRenderable } from "../ui";
|
|
3
3
|
import { colors } from "../theme/colors";
|
|
4
|
+
import { useAppInputCapture } from "../state/app/input-capture";
|
|
5
|
+
import { useShortcut } from "../react/input";
|
|
6
|
+
import { isPlainArrowDown, stopSearchFocusNavigation } from "../utils/search-focus-navigation";
|
|
4
7
|
|
|
5
8
|
export function InputSearchBar({
|
|
6
9
|
value,
|
|
@@ -12,6 +15,7 @@ export function InputSearchBar({
|
|
|
12
15
|
placeholder,
|
|
13
16
|
debounceMs,
|
|
14
17
|
normalizeValue = identity,
|
|
18
|
+
onNavigateDown,
|
|
15
19
|
onFocus,
|
|
16
20
|
onBlur,
|
|
17
21
|
onQueryChange,
|
|
@@ -25,11 +29,23 @@ export function InputSearchBar({
|
|
|
25
29
|
placeholder: string;
|
|
26
30
|
debounceMs: number;
|
|
27
31
|
normalizeValue?: (value: string) => string;
|
|
32
|
+
onNavigateDown?: () => void;
|
|
28
33
|
onFocus: () => void;
|
|
29
34
|
onBlur: () => void;
|
|
30
35
|
onQueryChange: (query: string) => void;
|
|
31
36
|
}) {
|
|
32
37
|
const [draft, setDraft] = useState(value);
|
|
38
|
+
useAppInputCapture(focused && active);
|
|
39
|
+
|
|
40
|
+
useShortcut((event) => {
|
|
41
|
+
if (!onNavigateDown || !isPlainArrowDown(event)) return;
|
|
42
|
+
stopSearchFocusNavigation(event);
|
|
43
|
+
onNavigateDown();
|
|
44
|
+
}, {
|
|
45
|
+
allowEditable: true,
|
|
46
|
+
enabled: focused && active && !!onNavigateDown,
|
|
47
|
+
phase: "before",
|
|
48
|
+
});
|
|
33
49
|
|
|
34
50
|
useEffect(() => {
|
|
35
51
|
setDraft(value);
|
|
@@ -10,7 +10,7 @@ import { useThemeColors } from "../../theme/theme-context";
|
|
|
10
10
|
import { hasPaneFooterContent, PaneFooterBar, PaneFooterProvider } from "./pane/footer";
|
|
11
11
|
import { PaneBodyFrame, getPaneWindowAttributes } from "./pane/frame";
|
|
12
12
|
import { PaneContent } from "./pane/content";
|
|
13
|
-
import { resolvePaneBodyFrame } from "./pane/sizing";
|
|
13
|
+
import { resolvePaneBodyFrame, shouldReservePaneFooter } from "./pane/sizing";
|
|
14
14
|
import { getPaneDisplayTitle } from "./pane/title";
|
|
15
15
|
import { TITLEBAR_OVERLAY_HEIGHT_PX, getTitlebarLeadingInset } from "./titlebar-overlay";
|
|
16
16
|
import { WindowControls, WINDOWS_CONTROL_GROUP_WIDTH_PX } from "./window-controls";
|
|
@@ -130,6 +130,8 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
130
130
|
<PaneFooterProvider>
|
|
131
131
|
{(footer) => {
|
|
132
132
|
const showFooter = hasPaneFooterContent(footer);
|
|
133
|
+
const reserveFooter = shouldReservePaneFooter(nativePaneChrome, showFooter);
|
|
134
|
+
const renderFooter = reserveFooter || showFooter;
|
|
133
135
|
const headerHeightRows = titleBarOverlay ? TITLEBAR_OVERLAY_HEIGHT_PX / cellHeightPx : 1;
|
|
134
136
|
const background = floatingPaneBg(focused);
|
|
135
137
|
const titleBackground = floatingPaneTitleBg(focused);
|
|
@@ -137,7 +139,8 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
137
139
|
width,
|
|
138
140
|
height,
|
|
139
141
|
nativePaneChrome,
|
|
140
|
-
|
|
142
|
+
footerVisible: renderFooter,
|
|
143
|
+
reserveFooter,
|
|
141
144
|
headerRows: headerHeightRows,
|
|
142
145
|
});
|
|
143
146
|
const bodyWidth = bodyFrame.width ?? 1;
|
|
@@ -208,7 +211,7 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
208
211
|
height={bodyHeight}
|
|
209
212
|
/>
|
|
210
213
|
</PaneBodyFrame>
|
|
211
|
-
{
|
|
214
|
+
{renderFooter && <PaneFooterBar footer={footer} focused={focused} width={width} />}
|
|
212
215
|
</Box>
|
|
213
216
|
);
|
|
214
217
|
}}
|
|
@@ -80,7 +80,8 @@ export function FloatingPaneWrapper({
|
|
|
80
80
|
const bg = floatingPaneBg(focused);
|
|
81
81
|
const showFooter = hasPaneFooterContent(footer);
|
|
82
82
|
const reserveFooter = shouldReservePaneFooter(nativePaneChrome, showFooter);
|
|
83
|
-
const
|
|
83
|
+
const renderFooter = reserveFooter || showFooter;
|
|
84
|
+
const bodyFrame = resolvePaneBodyFrame({ height, nativePaneChrome, footerVisible: renderFooter, reserveFooter });
|
|
84
85
|
|
|
85
86
|
return (
|
|
86
87
|
<Box
|
|
@@ -124,7 +125,7 @@ export function FloatingPaneWrapper({
|
|
|
124
125
|
{children}
|
|
125
126
|
</PaneBodyFrame>
|
|
126
127
|
|
|
127
|
-
{
|
|
128
|
+
{renderFooter && (
|
|
128
129
|
<PaneFooterBar
|
|
129
130
|
footer={footer}
|
|
130
131
|
focused={focused}
|
|
@@ -3,6 +3,7 @@ import { PaneInstanceProvider } from "../../../state/app/context";
|
|
|
3
3
|
import { PaneKeyboardScrollController } from "../../../state/pane-scroll-registry";
|
|
4
4
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
5
5
|
import type { PaneDef } from "../../../types/plugin";
|
|
6
|
+
import { Box } from "../../../ui";
|
|
6
7
|
|
|
7
8
|
interface PaneContentProps {
|
|
8
9
|
component: PaneDef["component"];
|
|
@@ -31,14 +32,25 @@ export const PaneContent = memo(function PaneContent({
|
|
|
31
32
|
return (
|
|
32
33
|
<PaneInstanceProvider paneId={paneId}>
|
|
33
34
|
<PaneKeyboardScrollController paneId={paneId} focused={focused} />
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
<Box
|
|
36
|
+
flexDirection="column"
|
|
37
|
+
flexGrow={1}
|
|
38
|
+
flexShrink={1}
|
|
39
|
+
flexBasis={0}
|
|
40
|
+
minWidth={0}
|
|
41
|
+
minHeight={0}
|
|
42
|
+
overflow="hidden"
|
|
43
|
+
data-gloom-role="pane-content"
|
|
44
|
+
>
|
|
45
|
+
<Component
|
|
46
|
+
paneId={paneId}
|
|
47
|
+
paneType={paneType}
|
|
48
|
+
focused={focused}
|
|
49
|
+
width={width}
|
|
50
|
+
height={height}
|
|
51
|
+
close={onClose ? close : undefined}
|
|
52
|
+
/>
|
|
53
|
+
</Box>
|
|
42
54
|
</PaneInstanceProvider>
|
|
43
55
|
);
|
|
44
56
|
});
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
useCallback,
|
|
4
4
|
useContext,
|
|
5
5
|
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
6
7
|
useMemo,
|
|
7
8
|
useRef,
|
|
8
9
|
useState,
|
|
@@ -17,6 +18,9 @@ import {
|
|
|
17
18
|
type PaneHint,
|
|
18
19
|
} from "./model";
|
|
19
20
|
|
|
21
|
+
const usePaneFooterRegistrationEffect =
|
|
22
|
+
typeof document === "undefined" ? useEffect : useLayoutEffect;
|
|
23
|
+
|
|
20
24
|
interface PaneFooterContextValue {
|
|
21
25
|
register(registrationId: string, registration: PaneFooterRegistration | null): void;
|
|
22
26
|
unregister(registrationId: string): void;
|
|
@@ -85,14 +89,14 @@ export function usePaneFooter(
|
|
|
85
89
|
const context = useContext(PaneFooterContext);
|
|
86
90
|
const previousRegistrationRef = useRef<PaneFooterRegistration | null>(null);
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
usePaneFooterRegistrationEffect(() => {
|
|
89
93
|
return () => {
|
|
90
94
|
previousRegistrationRef.current = null;
|
|
91
95
|
context?.unregister(registrationId);
|
|
92
96
|
};
|
|
93
97
|
}, [context, registrationId]);
|
|
94
98
|
|
|
95
|
-
|
|
99
|
+
usePaneFooterRegistrationEffect(() => {
|
|
96
100
|
if (!context) return;
|
|
97
101
|
const nextRegistration = factory() ?? null;
|
|
98
102
|
if (samePaneFooterRegistration(previousRegistrationRef.current, nextRegistration)) return;
|
|
@@ -19,13 +19,13 @@ export function getPaneWindowAttributes({
|
|
|
19
19
|
windowModeSelected?: boolean;
|
|
20
20
|
showBorderColor?: boolean;
|
|
21
21
|
}): Record<string, unknown> {
|
|
22
|
-
if (!enabled) return {};
|
|
23
|
-
|
|
24
22
|
const attributes: Record<string, unknown> = {
|
|
25
23
|
"data-gloom-role": role,
|
|
26
|
-
"data-focused": focused ? "true" : "false",
|
|
27
24
|
};
|
|
28
25
|
if (paneId) attributes["data-gloom-pane-id"] = paneId;
|
|
26
|
+
if (!enabled) return attributes;
|
|
27
|
+
|
|
28
|
+
attributes["data-focused"] = focused ? "true" : "false";
|
|
29
29
|
if (floating != null) attributes["data-floating"] = floating ? "true" : "false";
|
|
30
30
|
if (windowModeSelected != null) {
|
|
31
31
|
attributes["data-window-mode-selected"] = windowModeSelected ? "true" : "false";
|
|
@@ -48,7 +48,7 @@ export function PaneBodyFrame({
|
|
|
48
48
|
children: ReactNode;
|
|
49
49
|
}) {
|
|
50
50
|
return (
|
|
51
|
-
<Box {...layoutProps} overflow="hidden" backgroundColor={backgroundColor}>
|
|
51
|
+
<Box {...layoutProps} overflow="hidden" backgroundColor={backgroundColor} data-gloom-role="pane-body">
|
|
52
52
|
{children}
|
|
53
53
|
</Box>
|
|
54
54
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Box, Span, Text, useUiCapabilities } from "../../../ui";
|
|
2
|
-
import type
|
|
1
|
+
import { Box, Span, Text, useNativeRenderer, useUiCapabilities } from "../../../ui";
|
|
2
|
+
import { useCallback, useRef, type ReactNode } from "react";
|
|
3
3
|
import { blendHex, colors, floatingPaneTitleBg, paneTitleBg, paneTitleText } from "../../../theme/colors";
|
|
4
4
|
|
|
5
5
|
const PANE_HEADER_HEIGHT = 1;
|
|
@@ -29,6 +29,18 @@ function truncateTitle(title: string, maxWidth: number): string {
|
|
|
29
29
|
return `${title.slice(0, maxWidth - 2)}..`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function captureTerminalPointerDrag(renderer: unknown, renderable: unknown): void {
|
|
33
|
+
if (!renderable) return;
|
|
34
|
+
const hostCapture = (renderer as { captureMouseRenderable?: (target: unknown) => void }).captureMouseRenderable;
|
|
35
|
+
if (typeof hostCapture === "function") {
|
|
36
|
+
hostCapture.call(renderer, renderable);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const capture = (renderer as { setCapturedRenderable?: (target: unknown) => void }).setCapturedRenderable;
|
|
40
|
+
if (typeof capture !== "function") return;
|
|
41
|
+
capture.call(renderer, renderable);
|
|
42
|
+
}
|
|
43
|
+
|
|
32
44
|
function DesktopPaneButton({
|
|
33
45
|
icon,
|
|
34
46
|
onMouseDown,
|
|
@@ -107,11 +119,17 @@ export function PaneHeader({
|
|
|
107
119
|
onCloseMouseDown,
|
|
108
120
|
}: PaneHeaderProps) {
|
|
109
121
|
const { nativePaneChrome } = useUiCapabilities();
|
|
122
|
+
const nativeRenderer = useNativeRenderer();
|
|
123
|
+
const terminalHeaderRef = useRef<unknown>(null);
|
|
110
124
|
const visuallyFocused = focused || windowModeSelected;
|
|
111
125
|
const backgroundColor = floating ? floatingPaneTitleBg(visuallyFocused) : paneTitleBg(visuallyFocused);
|
|
112
126
|
const actionText = showActions ? PANE_HEADER_ACTION : " ";
|
|
113
127
|
const closeText = floating ? PANE_HEADER_CLOSE : "";
|
|
114
128
|
const textColor = paneTitleText(visuallyFocused, floating);
|
|
129
|
+
const handleTerminalHeaderMouseDown = useCallback((event: any) => {
|
|
130
|
+
captureTerminalPointerDrag(nativeRenderer, terminalHeaderRef.current);
|
|
131
|
+
onHeaderMouseDown?.(event);
|
|
132
|
+
}, [nativeRenderer, onHeaderMouseDown]);
|
|
115
133
|
|
|
116
134
|
if (nativePaneChrome) {
|
|
117
135
|
return (
|
|
@@ -201,7 +219,16 @@ export function PaneHeader({
|
|
|
201
219
|
const fill = "─".repeat(fillLen);
|
|
202
220
|
|
|
203
221
|
return (
|
|
204
|
-
<Box
|
|
222
|
+
<Box
|
|
223
|
+
ref={terminalHeaderRef}
|
|
224
|
+
height={PANE_HEADER_HEIGHT}
|
|
225
|
+
width={width}
|
|
226
|
+
backgroundColor={backgroundColor}
|
|
227
|
+
flexDirection="row"
|
|
228
|
+
onMouseDown={handleTerminalHeaderMouseDown}
|
|
229
|
+
onMouseDrag={onHeaderMouseDrag}
|
|
230
|
+
onMouseDragEnd={onHeaderMouseDragEnd}
|
|
231
|
+
>
|
|
205
232
|
<Text fg={borderColor} selectable={false}>{"┌─"}</Text>
|
|
206
233
|
<Text fg={textColor} selectable={false}>{`${PANE_HEADER_GRIP}${clippedTitle}`}</Text>
|
|
207
234
|
<Text fg={borderColor} selectable={false}>{fill}</Text>
|
|
@@ -229,7 +256,16 @@ export function PaneHeader({
|
|
|
229
256
|
const padding = " ".repeat(Math.max(0, titleWidth - clippedTitle.length));
|
|
230
257
|
|
|
231
258
|
return (
|
|
232
|
-
<Box
|
|
259
|
+
<Box
|
|
260
|
+
ref={terminalHeaderRef}
|
|
261
|
+
height={PANE_HEADER_HEIGHT}
|
|
262
|
+
width={width}
|
|
263
|
+
backgroundColor={backgroundColor}
|
|
264
|
+
flexDirection="row"
|
|
265
|
+
onMouseDown={handleTerminalHeaderMouseDown}
|
|
266
|
+
onMouseDrag={onHeaderMouseDrag}
|
|
267
|
+
onMouseDragEnd={onHeaderMouseDragEnd}
|
|
268
|
+
>
|
|
233
269
|
<Text fg={textColor} selectable={false}>
|
|
234
270
|
{`${PANE_HEADER_GRIP}${clippedTitle}${padding}`}
|
|
235
271
|
</Text>
|
|
@@ -49,9 +49,11 @@ export function PaneWrapper({
|
|
|
49
49
|
const bg = paneBg(focused);
|
|
50
50
|
const showFooter = hasPaneFooterContent(footer);
|
|
51
51
|
const reserveFooter = !!title && shouldReservePaneFooter(nativePaneChrome, showFooter);
|
|
52
|
+
const renderFooter = !!title && (reserveFooter || showFooter);
|
|
52
53
|
const bodyFrame = resolvePaneBodyFrame({
|
|
53
54
|
height: typeof height === "number" ? height : undefined,
|
|
54
55
|
nativePaneChrome,
|
|
56
|
+
footerVisible: renderFooter,
|
|
55
57
|
reserveFooter,
|
|
56
58
|
headerRows: title ? 1 : 0,
|
|
57
59
|
});
|
|
@@ -93,7 +95,7 @@ export function PaneWrapper({
|
|
|
93
95
|
<PaneBodyFrame layoutProps={bodyFrame.layoutProps} backgroundColor={bg}>
|
|
94
96
|
{children}
|
|
95
97
|
</PaneBodyFrame>
|
|
96
|
-
{
|
|
98
|
+
{renderFooter && (
|
|
97
99
|
<PaneFooterBar
|
|
98
100
|
footer={footer}
|
|
99
101
|
focused={focused}
|