gloomberb 0.9.4 → 0.9.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.
- package/README.md +49 -8
- package/package.json +4 -3
- package/patches/@opentui%2Fcore@0.3.2.patch +231 -29
- package/patches/react-reconciler@0.33.0.patch +10 -0
- package/src/app/global-shortcuts.test.tsx +20 -1
- package/src/app/global-shortcuts.ts +2 -0
- package/src/app/pane-runtime/index.tsx +11 -4
- package/src/app/pane-runtime/pane-settings-runtime.tsx +1 -1
- package/src/app.tsx +41 -17
- package/src/cli/commands/remote.ts +206 -0
- package/src/cli/entry.ts +8 -1
- package/src/cli/index.ts +2 -0
- package/src/cli/native-smoke.ts +5 -0
- package/src/components/chart/core/keyboard.ts +10 -1
- package/src/components/chart/native/bitmap-support.test.ts +27 -0
- package/src/components/chart/native/bitmap-support.ts +55 -0
- package/src/components/chart/native/kitty/index.test.ts +47 -0
- package/src/components/chart/native/surface/visibility.ts +8 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +4 -0
- package/src/components/chart/static/chart/bitmap.ts +16 -9
- package/src/components/chart/static/chart/surface.test.tsx +59 -4
- package/src/components/chart/static/chart/surface.tsx +4 -0
- package/src/components/chart/stock-chart/index.test.ts +7 -0
- package/src/components/command-bar/list/view.tsx +48 -1
- package/src/components/command-bar/multi-select-picker.tsx +7 -0
- package/src/components/command-bar/routing/navigation-state.ts +10 -1
- package/src/components/command-bar/surface/index.test.tsx +14 -0
- package/src/components/command-bar/theme-picker.tsx +91 -79
- package/src/components/command-bar/workflow/field-row.tsx +30 -5
- package/src/components/data-table/view.tsx +0 -12
- package/src/components/index.ts +7 -0
- package/src/components/layout/floating-pane.tsx +3 -3
- package/src/components/layout/pane/footer/index.tsx +23 -0
- package/src/components/layout/pane/header.tsx +5 -0
- package/src/components/layout/pane/index.tsx +3 -3
- package/src/components/layout/shell/action-menu-overlay.tsx +1 -1
- package/src/components/layout/shell/cursor-occlusion.ts +9 -1
- package/src/components/layout/shell/index.test.tsx +2 -0
- package/src/components/layout/shell/index.tsx +16 -4
- package/src/components/layout/shell/pane/actions.ts +21 -7
- package/src/components/layout/shell/pane/layers.tsx +2 -2
- package/src/components/layout/status-bar.tsx +5 -5
- package/src/components/metric-treemap/index.tsx +317 -0
- package/src/components/metric-treemap/layout.test.ts +77 -0
- package/src/components/metric-treemap/layout.ts +476 -0
- package/src/components/pane-settings-dialog/desktop.tsx +1 -1
- package/src/components/price-performance.tsx +1 -1
- package/src/components/price-sparkline/view.tsx +15 -14
- package/src/components/speedometer-gauge/terminal.tsx +18 -15
- package/src/components/table-view-shared.tsx +0 -10
- package/src/components/ticker/list-table-view.tsx +1 -7
- package/src/components/ticker/list-table.test.tsx +0 -4
- package/src/components/toggle-list.tsx +10 -0
- package/src/components/ui/button.tsx +12 -0
- package/src/components/ui/data-table/index.tsx +51 -0
- package/src/components/ui/data-table/opentui/index.tsx +4 -10
- package/src/components/ui/data-table/types.ts +6 -4
- package/src/components/ui/fields.tsx +18 -0
- package/src/components/ui/list-view.tsx +84 -4
- package/src/components/ui/message-composer.tsx +22 -0
- package/src/components/ui/tabs.tsx +46 -2
- package/src/components/ui/ui.test.tsx +0 -9
- package/src/core/state/app/layout-reducer.ts +7 -1
- package/src/core/state/app/layout.ts +6 -0
- package/src/core/state/app/state.ts +23 -1
- package/src/core/state/app/types.ts +4 -2
- package/src/plugins/builtin/account-management/form-components.tsx +2 -2
- package/src/plugins/builtin/alerts/pane.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +5 -18
- package/src/plugins/builtin/chat/chat.test.tsx +1 -7
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -1
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +1 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +2 -2
- package/src/plugins/builtin/chat/sidebar.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +2 -8
- package/src/plugins/builtin/cloud/auth-actions.tsx +2 -2
- package/src/plugins/builtin/cloud/plugin.tsx +4 -13
- package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +1 -1
- package/src/plugins/builtin/debug/index.tsx +3 -4
- package/src/plugins/builtin/help/components.tsx +1 -1
- package/src/plugins/builtin/holders/treemap.tsx +28 -297
- package/src/plugins/builtin/holders/types.ts +0 -34
- package/src/plugins/builtin/market-heatmap/data.test.ts +121 -0
- package/src/plugins/builtin/market-heatmap/data.ts +296 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +320 -0
- package/src/plugins/builtin/options/table.ts +5 -5
- package/src/plugins/builtin/plugin-groups.ts +2 -0
- package/src/plugins/builtin/portfolio-list/grid.tsx +197 -0
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +41 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +78 -22
- package/src/plugins/ibkr/gateway/service/index.test.ts +22 -0
- package/src/plugins/ibkr/gateway/service/index.ts +6 -9
- package/src/plugins/ibkr/gateway/service/native-loader.ts +12 -0
- package/src/plugins/ibkr/trade/ticket-panel.tsx +1 -1
- package/src/remote/app-host.tsx +77 -0
- package/src/remote/client.ts +34 -0
- package/src/remote/command-bar.ts +98 -0
- package/src/remote/controller-utils.ts +111 -0
- package/src/remote/controller.test.ts +365 -0
- package/src/remote/controller.ts +434 -0
- package/src/remote/endpoint.ts +41 -0
- package/src/remote/json-patch.test.ts +31 -0
- package/src/remote/json-patch.ts +89 -0
- package/src/remote/layout-helpers.ts +62 -0
- package/src/remote/resources.ts +186 -0
- package/src/remote/revision.ts +18 -0
- package/src/remote/schema.ts +143 -0
- package/src/remote/semantic-helpers.ts +161 -0
- package/src/remote/semantic-tree.tsx +112 -0
- package/src/remote/server.test.ts +50 -0
- package/src/remote/server.ts +121 -0
- package/src/remote/types.ts +119 -0
- package/src/renderers/electrobun/bun/index.ts +55 -1
- package/src/renderers/electrobun/shared/protocol.ts +11 -1
- package/src/renderers/electrobun/view/backend-rpc.ts +28 -1
- package/src/renderers/electrobun/view/build-assets.ts +1 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +0 -8
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -12
- package/src/renderers/electrobun/view/host/box.tsx +11 -1
- package/src/renderers/electrobun/view/host/mouse.test.ts +37 -0
- package/src/renderers/electrobun/view/host/mouse.ts +2 -0
- package/src/renderers/electrobun/view/host/style.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +5 -1
- package/src/renderers/electrobun/view/native-stubs/ibkr-native-loader.ts +3 -0
- package/src/renderers/electrobun/view/styles.css +7 -0
- package/src/renderers/opentui/chart-surface.test.tsx +101 -0
- package/src/renderers/opentui/chart-surface.tsx +15 -20
- package/src/renderers/opentui/native-smoke.ts +1 -0
- package/src/renderers/opentui/start.tsx +32 -0
- package/src/sources/yahoo-finance/http.ts +31 -4
- package/src/state/app/context/index.test.tsx +47 -0
- package/src/state/app/context/index.tsx +48 -18
- package/src/ui/host.tsx +1 -0
- package/src/ui/index.tsx +183 -7
- package/src/version.ts +1 -1
- package/src/plugins/builtin/holders/treemap-layout.ts +0 -231
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { act } from "react";
|
|
2
|
+
import { act, useEffect } from "react";
|
|
3
3
|
import { testRender } from "../../../../renderers/opentui/test-utils";
|
|
4
4
|
import { colors } from "../../../../theme/colors";
|
|
5
|
+
import { RemoteUiRegistryProvider, useRemoteUiRegistry, type RemoteUiRegistry } from "../../../../remote/semantic-tree";
|
|
5
6
|
import { resolveChartPalette } from "../../core/renderer";
|
|
6
7
|
import { StaticChartSurface } from "./surface";
|
|
7
8
|
import type { ProjectedChartPoint } from "../../core/data";
|
|
8
9
|
|
|
9
10
|
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
11
|
+
let remoteRegistry: RemoteUiRegistry | null = null;
|
|
10
12
|
|
|
11
13
|
afterEach(async () => {
|
|
12
|
-
|
|
14
|
+
const setup = testSetup;
|
|
15
|
+
testSetup = undefined;
|
|
16
|
+
remoteRegistry = null;
|
|
17
|
+
if (!setup) return;
|
|
13
18
|
await act(async () => {
|
|
14
|
-
|
|
19
|
+
setup.renderer.destroy();
|
|
15
20
|
});
|
|
16
|
-
testSetup = undefined;
|
|
17
21
|
});
|
|
18
22
|
|
|
19
23
|
const points: ProjectedChartPoint[] = [
|
|
@@ -22,6 +26,14 @@ const points: ProjectedChartPoint[] = [
|
|
|
22
26
|
{ date: new Date("2026-01-03"), open: 4.9, high: 4.9, low: 4.9, close: 4.9, volume: 0 },
|
|
23
27
|
];
|
|
24
28
|
|
|
29
|
+
function RemoteRegistryProbe() {
|
|
30
|
+
const registry = useRemoteUiRegistry();
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
remoteRegistry = registry;
|
|
33
|
+
}, [registry]);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
describe("StaticChartSurface", () => {
|
|
26
38
|
test("renders unit and custom y-axis labels", async () => {
|
|
27
39
|
testSetup = await testRender(
|
|
@@ -132,6 +144,49 @@ describe("StaticChartSurface", () => {
|
|
|
132
144
|
|
|
133
145
|
await act(async () => {
|
|
134
146
|
await testSetup!.mockMouse.moveTo(20, 4);
|
|
147
|
+
});
|
|
148
|
+
await act(async () => {
|
|
149
|
+
await testSetup!.renderOnce();
|
|
150
|
+
await testSetup!.renderOnce();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(testSetup.captureCharFrame()).not.toBe(initialFrame);
|
|
154
|
+
expect(testSetup.captureCharFrame()).toContain("X49");
|
|
155
|
+
expect(testSetup.captureCharFrame()).toContain("Y3.97");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("exposes semantic remote cursor control", async () => {
|
|
159
|
+
testSetup = await testRender(
|
|
160
|
+
<RemoteUiRegistryProvider>
|
|
161
|
+
<RemoteRegistryProbe />
|
|
162
|
+
<StaticChartSurface
|
|
163
|
+
points={points}
|
|
164
|
+
width={48}
|
|
165
|
+
height={10}
|
|
166
|
+
mode="line"
|
|
167
|
+
colors={resolveChartPalette(colors, "positive")}
|
|
168
|
+
xAxisLabels={["0%", "50%", "100%"]}
|
|
169
|
+
formatXAxisCursorValue={(ratio) => `X${Math.round(ratio * 100)}`}
|
|
170
|
+
formatYAxisValue={(value) => `Y${value.toFixed(2)}`}
|
|
171
|
+
/>
|
|
172
|
+
</RemoteUiRegistryProvider>,
|
|
173
|
+
{ width: 50, height: 12 },
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
await act(async () => {
|
|
177
|
+
await testSetup!.renderOnce();
|
|
178
|
+
await testSetup!.renderOnce();
|
|
179
|
+
});
|
|
180
|
+
const initialFrame = testSetup.captureCharFrame();
|
|
181
|
+
const chartNode = remoteRegistry?.snapshot().find((node) => (
|
|
182
|
+
node.role === "chart" && node.metadata?.kind === "static-chart"
|
|
183
|
+
));
|
|
184
|
+
expect(chartNode?.actions).toContain("moveCursor");
|
|
185
|
+
|
|
186
|
+
await act(async () => {
|
|
187
|
+
await remoteRegistry!.invoke(chartNode!.id, "moveCursor", { x: 20, y: 4 });
|
|
188
|
+
});
|
|
189
|
+
await act(async () => {
|
|
135
190
|
await testSetup!.renderOnce();
|
|
136
191
|
await testSetup!.renderOnce();
|
|
137
192
|
});
|
|
@@ -253,6 +253,10 @@ export function StaticChartSurface({
|
|
|
253
253
|
flexDirection="column"
|
|
254
254
|
bitmaps={bitmap ? [bitmap] : null}
|
|
255
255
|
crosshair={canvasCrosshair}
|
|
256
|
+
onMouseMove={handleCursorEvent}
|
|
257
|
+
onMouseDown={handleCursorEvent}
|
|
258
|
+
onMouseOut={clearCursor}
|
|
259
|
+
data-gloom-remote-kind="static-chart"
|
|
256
260
|
>
|
|
257
261
|
{textResult.lines.map((line, index) => (
|
|
258
262
|
<Text key={index} content={line} />
|
|
@@ -159,6 +159,13 @@ describe("stock chart keyboard helpers", () => {
|
|
|
159
159
|
expect(resolveChartKeyboardKey({ name: "minus" })).toBe("zoom-out");
|
|
160
160
|
expect(resolveChartKeyboardKey({ name: "A" })).toBe("a");
|
|
161
161
|
});
|
|
162
|
+
|
|
163
|
+
test("ignores shortcut-modified keys so app shortcuts can handle them", () => {
|
|
164
|
+
expect(resolveChartKeyboardKey({ name: "1", ctrl: true })).toBe("");
|
|
165
|
+
expect(resolveChartKeyboardKey({ name: "2", meta: true })).toBe("");
|
|
166
|
+
expect(resolveChartKeyboardKey({ name: "3", super: true })).toBe("");
|
|
167
|
+
expect(resolveChartKeyboardKey({ name: "4", alt: true })).toBe("");
|
|
168
|
+
});
|
|
162
169
|
});
|
|
163
170
|
|
|
164
171
|
describe("stock chart viewport helpers", () => {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { Spinner } from "../../ui";
|
|
11
11
|
import type { CommandBarListRow, ListScreenState, ResultItem } from "./model";
|
|
12
12
|
import { getRowPresentation, truncateText } from "../view-model";
|
|
13
|
+
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
13
14
|
|
|
14
15
|
export type CommandBarListScrollEvent = {
|
|
15
16
|
stopPropagation: () => void;
|
|
@@ -70,6 +71,8 @@ export const CommandBarListHeader = memo(function CommandBarListHeader({
|
|
|
70
71
|
onChange={onQueryChange}
|
|
71
72
|
placeholder={kind === "root" ? "Search commands" : title === "Security Description" ? "Search tickers" : "Filter"}
|
|
72
73
|
focused
|
|
74
|
+
data-gloom-remote-scope="command-bar"
|
|
75
|
+
data-gloom-remote-surface="command-bar"
|
|
73
76
|
width={nativePaneChrome ? "100%" : queryDisplayWidth}
|
|
74
77
|
backgroundColor={nativePaneChrome ? "transparent" : paletteBg}
|
|
75
78
|
focusedBackgroundColor={nativePaneChrome ? "transparent" : paletteBg}
|
|
@@ -114,6 +117,9 @@ interface CommandBarListItemRowProps {
|
|
|
114
117
|
globalIdx: number;
|
|
115
118
|
isSelected: boolean;
|
|
116
119
|
isHovered: boolean;
|
|
120
|
+
listKind: ListScreenState["kind"];
|
|
121
|
+
listTitle: string;
|
|
122
|
+
listQuery: string;
|
|
117
123
|
contentPadding: number;
|
|
118
124
|
labelWidth: number;
|
|
119
125
|
trailingWidth: number;
|
|
@@ -135,6 +141,9 @@ const CommandBarListItemRow = memo(function CommandBarListItemRow({
|
|
|
135
141
|
globalIdx,
|
|
136
142
|
isSelected,
|
|
137
143
|
isHovered,
|
|
144
|
+
listKind,
|
|
145
|
+
listTitle,
|
|
146
|
+
listQuery,
|
|
138
147
|
contentPadding,
|
|
139
148
|
labelWidth,
|
|
140
149
|
trailingWidth,
|
|
@@ -153,6 +162,41 @@ const CommandBarListItemRow = memo(function CommandBarListItemRow({
|
|
|
153
162
|
const presentation = getRowPresentation(item, isSelected, trailingWidth > 0);
|
|
154
163
|
const label = truncateText(presentation.label, labelWidth);
|
|
155
164
|
const trailing = truncateText(presentation.trailing, trailingWidth);
|
|
165
|
+
const activate = () => onRowMouseDown({
|
|
166
|
+
preventDefault() {},
|
|
167
|
+
stopPropagation() {},
|
|
168
|
+
}, item, globalIdx);
|
|
169
|
+
useRemoteUiNode({
|
|
170
|
+
role: "command-bar-result",
|
|
171
|
+
label: item.label,
|
|
172
|
+
disabled: item.disabled === true,
|
|
173
|
+
actions: {
|
|
174
|
+
activate,
|
|
175
|
+
press: activate,
|
|
176
|
+
secondary: item.secondaryAction ? () => item.secondaryAction?.() : undefined,
|
|
177
|
+
},
|
|
178
|
+
metadata: {
|
|
179
|
+
index: globalIdx,
|
|
180
|
+
selected: isSelected,
|
|
181
|
+
hovered: isHovered,
|
|
182
|
+
listKind,
|
|
183
|
+
listTitle,
|
|
184
|
+
listQuery,
|
|
185
|
+
item: {
|
|
186
|
+
id: item.id,
|
|
187
|
+
label: item.label,
|
|
188
|
+
detail: item.detail,
|
|
189
|
+
category: item.category,
|
|
190
|
+
kind: item.kind,
|
|
191
|
+
right: item.right,
|
|
192
|
+
shortcutQuery: item.shortcutQuery,
|
|
193
|
+
searchText: item.searchText,
|
|
194
|
+
checked: item.checked,
|
|
195
|
+
current: item.current,
|
|
196
|
+
disabled: item.disabled === true,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
});
|
|
156
200
|
|
|
157
201
|
return (
|
|
158
202
|
<Box
|
|
@@ -165,7 +209,7 @@ const CommandBarListItemRow = memo(function CommandBarListItemRow({
|
|
|
165
209
|
: isHovered
|
|
166
210
|
? paletteHoverBg
|
|
167
211
|
: (nativePaneChrome ? panelBg : paletteBg)}
|
|
168
|
-
|
|
212
|
+
onMouseOver={() => onHoverIndex(globalIdx)}
|
|
169
213
|
onMouseOut={() => onHoverIndex(null)}
|
|
170
214
|
{...(!nativePaneChrome ? { onMouseScroll: onListScroll } : {})}
|
|
171
215
|
onMouseDown={(event: any) => onRowMouseDown(event, item, globalIdx)}
|
|
@@ -283,6 +327,9 @@ export const CommandBarListBody = memo(function CommandBarListBody({
|
|
|
283
327
|
globalIdx={row.globalIdx}
|
|
284
328
|
isSelected={isSelected}
|
|
285
329
|
isHovered={isHovered}
|
|
330
|
+
listKind={visibleListState.kind}
|
|
331
|
+
listTitle={visibleListState.title}
|
|
332
|
+
listQuery={visibleListState.query}
|
|
286
333
|
contentPadding={contentPadding}
|
|
287
334
|
labelWidth={labelWidth}
|
|
288
335
|
trailingWidth={trailingWidth}
|
|
@@ -197,6 +197,13 @@ export function CommandBarMultiSelectBody({
|
|
|
197
197
|
onSelect={onSelect}
|
|
198
198
|
onToggle={onToggle}
|
|
199
199
|
bgColor={nativePaneChrome ? panelBg : paletteBg}
|
|
200
|
+
remoteLabel={route.title}
|
|
201
|
+
remoteScope="command-bar"
|
|
202
|
+
remoteMetadata={{
|
|
203
|
+
surface: "multi-select-picker",
|
|
204
|
+
routeKind: route.kind,
|
|
205
|
+
pickerId: route.pickerId,
|
|
206
|
+
}}
|
|
200
207
|
/>
|
|
201
208
|
<Box flexDirection="row" gap={1}>
|
|
202
209
|
<Button label="Done" variant="primary" onPress={onCommit} />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useRef, useState, type Dispatch, type RefObject, type SetStateAction } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState, type Dispatch, type RefObject, type SetStateAction } from "react";
|
|
2
2
|
import type { AppAction } from "../../../state/app/context";
|
|
3
3
|
import type { Command } from "../commands/registry";
|
|
4
4
|
import { resolveCommandBarMode } from "../view-model";
|
|
@@ -63,6 +63,15 @@ export function useCommandBarNavigationState({
|
|
|
63
63
|
const currentRouteRef = useRef<CommandBarRoute | null>(currentRoute);
|
|
64
64
|
currentRouteRef.current = currentRoute;
|
|
65
65
|
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (currentRouteRef.current) return;
|
|
68
|
+
if (initialQuery === rootQueryRef.current) return;
|
|
69
|
+
rootQueryRef.current = initialQuery;
|
|
70
|
+
setRootQueryValue(initialQuery);
|
|
71
|
+
setRootSelectedIdx(0);
|
|
72
|
+
setRootHoveredIdx(null);
|
|
73
|
+
}, [initialQuery]);
|
|
74
|
+
|
|
66
75
|
const closeAll = useCallback((options?: CloseCommandBarOptions) => {
|
|
67
76
|
if (options?.revertThemePreview !== false) {
|
|
68
77
|
restoreThemePreview();
|
|
@@ -129,6 +129,20 @@ describe("CommandBar", () => {
|
|
|
129
129
|
expect(testSetup.captureCharFrame()).not.toContain("Commands");
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
+
test("shows theme picker rows and commits a filtered light theme", async () => {
|
|
133
|
+
testSetup = await testRender(<CommandBarHarness query="TH light" live />, {
|
|
134
|
+
width: 80,
|
|
135
|
+
height: 24,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await testSetup.renderOnce();
|
|
139
|
+
expect(testSetup.captureCharFrame()).toContain("GitHub Light");
|
|
140
|
+
|
|
141
|
+
await clickFrameText("GitHub Light");
|
|
142
|
+
await waitForFrameToContain("theme:github-light");
|
|
143
|
+
expect(testSetup.captureCharFrame()).not.toContain("GitHub Light");
|
|
144
|
+
});
|
|
145
|
+
|
|
132
146
|
test("runs plugin command shortcuts from the root query", async () => {
|
|
133
147
|
const calls: string[] = [];
|
|
134
148
|
|
|
@@ -4,13 +4,14 @@ import {
|
|
|
4
4
|
useCallback,
|
|
5
5
|
useEffect,
|
|
6
6
|
useImperativeHandle,
|
|
7
|
-
useLayoutEffect,
|
|
8
7
|
useMemo,
|
|
9
8
|
useRef,
|
|
10
9
|
useState,
|
|
11
10
|
} from "react";
|
|
12
|
-
import { Box,
|
|
13
|
-
import { TextAttributes
|
|
11
|
+
import { Box, Text } from "../../ui";
|
|
12
|
+
import { TextAttributes } from "../../ui";
|
|
13
|
+
import { ListView, type ListViewItem } from "../ui";
|
|
14
|
+
import type { ListRowState } from "../ui/list-view";
|
|
14
15
|
import { getThemeIds, themes as themeRegistry } from "../../theme/themes";
|
|
15
16
|
import { truncateText } from "./view-model";
|
|
16
17
|
|
|
@@ -75,7 +76,6 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
|
|
|
75
76
|
onPreview,
|
|
76
77
|
onCommit,
|
|
77
78
|
}: ThemePickerProps, ref) {
|
|
78
|
-
const scrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
79
79
|
const previewTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
80
80
|
const pendingPreviewIdRef = useRef<string | null>(null);
|
|
81
81
|
const committedThemeIdRef = useRef(committedThemeId);
|
|
@@ -92,9 +92,20 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
|
|
|
92
92
|
const [selectedIndex, setSelectedIndex] = useState(() => (
|
|
93
93
|
Math.max(0, themes.findIndex((theme) => theme.id === committedThemeId))
|
|
94
94
|
));
|
|
95
|
-
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
96
95
|
const themesRef = useRef(themes);
|
|
97
96
|
const selectedIndexRef = useRef(selectedIndex);
|
|
97
|
+
const items = useMemo<ListViewItem[]>(() => themes.map((theme) => {
|
|
98
|
+
const current = theme.id === committedThemeId;
|
|
99
|
+
return {
|
|
100
|
+
id: theme.id,
|
|
101
|
+
label: theme.name,
|
|
102
|
+
detail: current ? "current" : "",
|
|
103
|
+
category: "Themes",
|
|
104
|
+
kind: "theme",
|
|
105
|
+
right: current ? "current" : "",
|
|
106
|
+
current,
|
|
107
|
+
};
|
|
108
|
+
}), [committedThemeId, themes]);
|
|
98
109
|
|
|
99
110
|
themesRef.current = themes;
|
|
100
111
|
selectedIndexRef.current = selectedIndex;
|
|
@@ -131,7 +142,6 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
|
|
|
131
142
|
if (nextIndex === selectedIndexRef.current) return false;
|
|
132
143
|
selectedIndexRef.current = nextIndex;
|
|
133
144
|
setSelectedIndex(nextIndex);
|
|
134
|
-
setHoveredIndex(null);
|
|
135
145
|
requestPreview(options[nextIndex]!.id);
|
|
136
146
|
return true;
|
|
137
147
|
}, [requestPreview]);
|
|
@@ -155,24 +165,10 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
|
|
|
155
165
|
const nextIndex = preferredIndex >= 0 ? preferredIndex : 0;
|
|
156
166
|
selectedIndexRef.current = nextIndex;
|
|
157
167
|
setSelectedIndex(nextIndex);
|
|
158
|
-
setHoveredIndex(null);
|
|
159
168
|
}, [committedThemeId, themes]);
|
|
160
169
|
|
|
161
170
|
useEffect(() => cancelPreview, [cancelPreview]);
|
|
162
171
|
|
|
163
|
-
useLayoutEffect(() => {
|
|
164
|
-
const scrollBox = scrollRef.current;
|
|
165
|
-
if (!scrollBox) return;
|
|
166
|
-
if (scrollBox.verticalScrollBar) scrollBox.verticalScrollBar.visible = false;
|
|
167
|
-
if (themes.length === 0) return;
|
|
168
|
-
const viewportHeight = Math.max(1, scrollBox.viewport?.height ?? height);
|
|
169
|
-
if (selectedIndex < scrollBox.scrollTop) {
|
|
170
|
-
scrollBox.scrollTo(selectedIndex);
|
|
171
|
-
} else if (selectedIndex >= scrollBox.scrollTop + viewportHeight) {
|
|
172
|
-
scrollBox.scrollTo(selectedIndex - viewportHeight + 1);
|
|
173
|
-
}
|
|
174
|
-
}, [height, selectedIndex, themes.length]);
|
|
175
|
-
|
|
176
172
|
const handleScroll = useCallback((event: ThemePickerScrollEvent) => {
|
|
177
173
|
event.stopPropagation();
|
|
178
174
|
event.preventDefault();
|
|
@@ -185,70 +181,86 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
|
|
|
185
181
|
}
|
|
186
182
|
}, [move]);
|
|
187
183
|
|
|
188
|
-
|
|
184
|
+
const handleSelect = useCallback((index: number) => {
|
|
185
|
+
const selected = themesRef.current[index];
|
|
186
|
+
if (!selected) return;
|
|
187
|
+
selectedIndexRef.current = index;
|
|
188
|
+
setSelectedIndex(index);
|
|
189
|
+
requestPreview(selected.id);
|
|
190
|
+
}, [requestPreview]);
|
|
191
|
+
|
|
192
|
+
const handleActivate = useCallback((item: ListViewItem, index: number) => {
|
|
193
|
+
const selected = themesRef.current[index] ?? themesRef.current.find((theme) => theme.id === item.id);
|
|
194
|
+
if (!selected) return;
|
|
195
|
+
selectedIndexRef.current = index;
|
|
196
|
+
setSelectedIndex(index);
|
|
197
|
+
cancelPreview();
|
|
198
|
+
onCommitRef.current(selected.id);
|
|
199
|
+
}, [cancelPreview]);
|
|
200
|
+
|
|
201
|
+
const renderRow = useCallback((item: ListViewItem, state: ListRowState) => {
|
|
202
|
+
const label = truncateText(item.label, labelWidth);
|
|
203
|
+
const trailing = item.current ? "current" : "";
|
|
189
204
|
return (
|
|
190
|
-
<Box
|
|
191
|
-
|
|
205
|
+
<Box
|
|
206
|
+
flexDirection="row"
|
|
207
|
+
height={1}
|
|
208
|
+
paddingX={contentPadding}
|
|
209
|
+
width="100%"
|
|
210
|
+
data-command-bar-row-selected={nativePaneChrome && state.selected ? "true" : undefined}
|
|
211
|
+
style={nativePaneChrome ? { borderRadius: 6 } : undefined}
|
|
212
|
+
>
|
|
213
|
+
<Box width={labelWidth}>
|
|
214
|
+
<Text
|
|
215
|
+
fg={state.selected ? paletteSelectedText : paletteText}
|
|
216
|
+
attributes={item.current ? TextAttributes.BOLD : undefined}
|
|
217
|
+
>
|
|
218
|
+
{label}
|
|
219
|
+
</Text>
|
|
220
|
+
</Box>
|
|
221
|
+
<Box width={trailingWidth}>
|
|
222
|
+
<Text fg={state.selected ? paletteSelectedText : paletteSubtleText}>
|
|
223
|
+
{truncateText(trailing, trailingWidth)}
|
|
224
|
+
</Text>
|
|
225
|
+
</Box>
|
|
192
226
|
</Box>
|
|
193
227
|
);
|
|
194
|
-
}
|
|
228
|
+
}, [
|
|
229
|
+
contentPadding,
|
|
230
|
+
labelWidth,
|
|
231
|
+
nativePaneChrome,
|
|
232
|
+
paletteSelectedText,
|
|
233
|
+
paletteSubtleText,
|
|
234
|
+
paletteText,
|
|
235
|
+
trailingWidth,
|
|
236
|
+
]);
|
|
195
237
|
|
|
196
238
|
return (
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
239
|
+
<ListView
|
|
240
|
+
items={items}
|
|
241
|
+
selectedIndex={selectedIndex}
|
|
200
242
|
height={height}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
{
|
|
204
|
-
|
|
205
|
-
{
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
onMouseOut={() => setHoveredIndex(null)}
|
|
224
|
-
onMouseDown={(event: any) => {
|
|
225
|
-
event.stopPropagation?.();
|
|
226
|
-
event.preventDefault?.();
|
|
227
|
-
selectedIndexRef.current = index;
|
|
228
|
-
setSelectedIndex(index);
|
|
229
|
-
cancelPreview();
|
|
230
|
-
onCommitRef.current(theme.id);
|
|
231
|
-
}}
|
|
232
|
-
{...(!nativePaneChrome ? { onMouseScroll: handleScroll } : {})}
|
|
233
|
-
data-command-bar-row-selected={nativePaneChrome && selected ? "true" : undefined}
|
|
234
|
-
style={nativePaneChrome ? { borderRadius: 6 } : undefined}
|
|
235
|
-
>
|
|
236
|
-
<Box width={labelWidth}>
|
|
237
|
-
<Text
|
|
238
|
-
fg={selected ? paletteSelectedText : paletteText}
|
|
239
|
-
attributes={current ? TextAttributes.BOLD : undefined}
|
|
240
|
-
>
|
|
241
|
-
{label}
|
|
242
|
-
</Text>
|
|
243
|
-
</Box>
|
|
244
|
-
<Box width={trailingWidth}>
|
|
245
|
-
<Text fg={selected ? paletteSelectedText : paletteSubtleText}>
|
|
246
|
-
{truncateText(trailing, trailingWidth)}
|
|
247
|
-
</Text>
|
|
248
|
-
</Box>
|
|
249
|
-
</Box>
|
|
250
|
-
);
|
|
251
|
-
})}
|
|
252
|
-
</ScrollBox>
|
|
243
|
+
scrollable
|
|
244
|
+
rowGap={0}
|
|
245
|
+
rowHeight={1}
|
|
246
|
+
surface="plain"
|
|
247
|
+
bgColor={nativePaneChrome ? panelBg : paletteBg}
|
|
248
|
+
selectedBgColor={paletteSelectedBg}
|
|
249
|
+
hoverBgColor={paletteHoverBg}
|
|
250
|
+
emptyMessage={truncateText("No themes match", queryDisplayWidth)}
|
|
251
|
+
showSelectedDescription={false}
|
|
252
|
+
onSelect={handleSelect}
|
|
253
|
+
onActivate={handleActivate}
|
|
254
|
+
onMouseScroll={!nativePaneChrome ? handleScroll : undefined}
|
|
255
|
+
renderRow={renderRow}
|
|
256
|
+
remoteLabel="Theme picker"
|
|
257
|
+
remoteScope="command-bar"
|
|
258
|
+
remoteItemKind="theme"
|
|
259
|
+
remoteItemCategory="Themes"
|
|
260
|
+
remoteMetadata={{
|
|
261
|
+
surface: "theme-picker",
|
|
262
|
+
filter: normalizedFilter,
|
|
263
|
+
}}
|
|
264
|
+
/>
|
|
253
265
|
);
|
|
254
266
|
}));
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isWorkflowTextField,
|
|
16
16
|
summarizeWorkflowFieldValue,
|
|
17
17
|
} from "../helpers";
|
|
18
|
+
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
18
19
|
import type {
|
|
19
20
|
CommandBarFieldValue,
|
|
20
21
|
CommandBarWorkflowField,
|
|
@@ -84,6 +85,34 @@ export function CommandBarWorkflowFieldRow({
|
|
|
84
85
|
onMoveFieldFocus(1);
|
|
85
86
|
}
|
|
86
87
|
};
|
|
88
|
+
const focusOrOpenField = () => {
|
|
89
|
+
onActiveTextareaSync(route);
|
|
90
|
+
onFieldFocus(field.id);
|
|
91
|
+
if (!isWorkflowTextField(field) && !useNativeSelect) {
|
|
92
|
+
onFieldPickerOpen(route, field);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
useRemoteUiNode({
|
|
97
|
+
role: "command-bar-field",
|
|
98
|
+
label: field.label,
|
|
99
|
+
disabled: route.pending,
|
|
100
|
+
actions: {
|
|
101
|
+
focus: focusOrOpenField,
|
|
102
|
+
open: !isWorkflowTextField(field) && !useNativeSelect ? focusOrOpenField : undefined,
|
|
103
|
+
submit: () => void onSubmit(route),
|
|
104
|
+
},
|
|
105
|
+
metadata: {
|
|
106
|
+
scope: "command-bar",
|
|
107
|
+
surface: "workflow",
|
|
108
|
+
routeTitle: route.title,
|
|
109
|
+
fieldId: field.id,
|
|
110
|
+
fieldType: field.type,
|
|
111
|
+
active,
|
|
112
|
+
value: summarizeWorkflowFieldValue(field, value),
|
|
113
|
+
description: fieldDescription,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
87
116
|
|
|
88
117
|
return (
|
|
89
118
|
<Box
|
|
@@ -93,11 +122,7 @@ export function CommandBarWorkflowFieldRow({
|
|
|
93
122
|
backgroundColor={fieldBg}
|
|
94
123
|
onMouseDown={(event: any) => {
|
|
95
124
|
event.stopPropagation?.();
|
|
96
|
-
|
|
97
|
-
onFieldFocus(field.id);
|
|
98
|
-
if (!isWorkflowTextField(field) && !useNativeSelect) {
|
|
99
|
-
onFieldPickerOpen(route, field);
|
|
100
|
-
}
|
|
125
|
+
focusOrOpenField();
|
|
101
126
|
}}
|
|
102
127
|
style={nativePaneChrome ? {
|
|
103
128
|
marginBottom: isLastField ? 8 : 10,
|
|
@@ -73,8 +73,6 @@ export interface DataTableViewProps<
|
|
|
73
73
|
| "scrollRef"
|
|
74
74
|
| "syncHeaderScroll"
|
|
75
75
|
| "onBodyScrollActivity"
|
|
76
|
-
| "hoveredIdx"
|
|
77
|
-
| "setHoveredIdx"
|
|
78
76
|
| "isSelected"
|
|
79
77
|
| "onSelect"
|
|
80
78
|
| "onActivate"
|
|
@@ -97,8 +95,6 @@ export interface DataTableViewProps<
|
|
|
97
95
|
scrollRef?: RefObject<ScrollBoxRenderable | null>;
|
|
98
96
|
syncHeaderScroll?: () => void;
|
|
99
97
|
onBodyScrollActivity?: () => void;
|
|
100
|
-
hoveredIdx?: number | null;
|
|
101
|
-
setHoveredIdx?: (index: number | null) => void;
|
|
102
98
|
keyboardNavigation?: boolean;
|
|
103
99
|
onRootKeyDown?: (
|
|
104
100
|
event: DataTableKeyEvent,
|
|
@@ -125,8 +121,6 @@ export function DataTableView<
|
|
|
125
121
|
scrollRef,
|
|
126
122
|
syncHeaderScroll,
|
|
127
123
|
onBodyScrollActivity,
|
|
128
|
-
hoveredIdx,
|
|
129
|
-
setHoveredIdx,
|
|
130
124
|
keyboardNavigation = true,
|
|
131
125
|
onRootKeyDown,
|
|
132
126
|
resetScrollKey,
|
|
@@ -137,15 +131,11 @@ export function DataTableView<
|
|
|
137
131
|
const {
|
|
138
132
|
effectiveHeaderScrollRef,
|
|
139
133
|
effectiveScrollRef,
|
|
140
|
-
effectiveHoveredIdx,
|
|
141
|
-
effectiveSetHoveredIdx,
|
|
142
134
|
effectiveSyncHeaderScroll,
|
|
143
135
|
} = useTableViewState({
|
|
144
136
|
headerScrollRef,
|
|
145
137
|
scrollRef,
|
|
146
138
|
syncHeaderScroll,
|
|
147
|
-
hoveredIdx,
|
|
148
|
-
setHoveredIdx,
|
|
149
139
|
});
|
|
150
140
|
const [cursorIndex, setCursorIndex] = useState<number | null>(null);
|
|
151
141
|
const pendingCommitRef = useRef(false);
|
|
@@ -532,8 +522,6 @@ export function DataTableView<
|
|
|
532
522
|
scrollRef={effectiveScrollRef}
|
|
533
523
|
syncHeaderScroll={effectiveSyncHeaderScroll}
|
|
534
524
|
onBodyScrollActivity={handleBodyScrollActivity}
|
|
535
|
-
hoveredIdx={effectiveHoveredIdx}
|
|
536
|
-
setHoveredIdx={effectiveSetHoveredIdx}
|
|
537
525
|
scrollToIndex={effectiveScrollToIndex}
|
|
538
526
|
scrollToIndexVersion={scrollToIndexVersion + selectionScrollVersion}
|
|
539
527
|
isSelected={isItemSelected}
|
package/src/components/index.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
export { PriceSelectorDialog } from "./price-selector-dialog";
|
|
3
3
|
export { StaticChartSurface } from "./chart/static/chart/surface";
|
|
4
|
+
export {
|
|
5
|
+
buildMetricTreemapNavigationTiles,
|
|
6
|
+
findMetricTreemapNeighbor,
|
|
7
|
+
MetricTreemapSurface,
|
|
8
|
+
type MetricTreemapDirection,
|
|
9
|
+
type MetricTreemapItem,
|
|
10
|
+
} from "./metric-treemap";
|
|
4
11
|
export { SpeedometerGauge } from "./speedometer-gauge";
|
|
5
12
|
export type { SpeedometerSegment } from "./speedometer-gauge";
|
|
6
13
|
export { TickerListTableView } from "./ticker/list-table-view";
|
|
@@ -18,7 +18,7 @@ interface FloatingPaneWrapperProps {
|
|
|
18
18
|
windowModeSelected?: boolean;
|
|
19
19
|
showActions?: boolean;
|
|
20
20
|
onMouseDown?: (event: any) => void;
|
|
21
|
-
|
|
21
|
+
onHeaderMouseMove?: (event: any) => void;
|
|
22
22
|
onHeaderMouseDown?: (event: any) => void;
|
|
23
23
|
onHeaderMouseDrag?: (event: any) => void;
|
|
24
24
|
onHeaderMouseDragEnd?: (event: any) => void;
|
|
@@ -63,7 +63,7 @@ export function FloatingPaneWrapper({
|
|
|
63
63
|
windowModeSelected = false,
|
|
64
64
|
showActions = false,
|
|
65
65
|
onMouseDown,
|
|
66
|
-
|
|
66
|
+
onHeaderMouseMove,
|
|
67
67
|
onHeaderMouseDown,
|
|
68
68
|
onHeaderMouseDrag,
|
|
69
69
|
onHeaderMouseDragEnd,
|
|
@@ -104,7 +104,6 @@ export function FloatingPaneWrapper({
|
|
|
104
104
|
showBorderColor: true,
|
|
105
105
|
})}
|
|
106
106
|
onMouseDown={onMouseDown}
|
|
107
|
-
onMouseMove={onMouseMove}
|
|
108
107
|
>
|
|
109
108
|
<PaneHeader
|
|
110
109
|
title={title}
|
|
@@ -113,6 +112,7 @@ export function FloatingPaneWrapper({
|
|
|
113
112
|
windowModeSelected={windowModeSelected}
|
|
114
113
|
floating
|
|
115
114
|
showActions={showActions}
|
|
115
|
+
onHeaderMouseMove={onHeaderMouseMove}
|
|
116
116
|
onHeaderMouseDown={onHeaderMouseDown}
|
|
117
117
|
onHeaderMouseDrag={onHeaderMouseDrag}
|
|
118
118
|
onHeaderMouseDragEnd={onHeaderMouseDragEnd}
|