gloomberb 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/auth.ts +20 -0
- package/src/api-client/index.test.ts +3 -3
- package/src/api-client/index.ts +48 -6
- package/src/api-client/request.test.ts +63 -0
- package/src/api-client/request.ts +53 -5
- package/src/api-client/types.ts +45 -2
- package/src/app/pane-runtime/layout-placement.ts +0 -1
- package/src/app/runtime/desktop-deeplink.test.ts +87 -0
- package/src/app/runtime/desktop-deeplink.ts +496 -0
- package/src/app.tsx +34 -0
- package/src/brokers/account-cache.ts +2 -10
- package/src/brokers/broker-portfolio-sync.ts +0 -1
- package/src/brokers/broker-ticker-sync.ts +20 -16
- package/src/brokers/remote-broker-adapter.ts +3 -0
- package/src/capabilities/registry.test.ts +25 -0
- package/src/capabilities/registry.ts +22 -5
- package/src/cli/commands/automation.ts +1 -3
- package/src/cli/commands/command-utils.ts +0 -7
- package/src/cli/commands/overview.ts +2 -2
- package/src/cli/commands/plugins.ts +34 -17
- package/src/cli/index.ts +2 -2
- package/src/cli.test.ts +0 -157
- package/src/components/chart/bar-chart-renderer.test.ts +87 -12
- package/src/components/chart/bar-chart-renderer.ts +111 -55
- package/src/components/chart/comparison/renderer.ts +0 -1
- package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
- package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
- package/src/components/chart/core/index.test.ts +0 -24
- package/src/components/chart/core/renderer.ts +0 -4
- package/src/components/chart/core/resolution.test.ts +0 -17
- package/src/components/chart/core/resolution.ts +0 -26
- package/src/components/chart/core/scroll.test.ts +0 -6
- package/src/components/chart/core/scroll.ts +0 -22
- package/src/components/chart/core/viewport.test.ts +2 -23
- package/src/components/chart/core/viewport.ts +0 -27
- package/src/components/chart/market-session.ts +1 -1
- package/src/components/chart/native/surface/hooks.ts +10 -3
- package/src/components/chart/static/bar-chart-surface.tsx +104 -20
- package/src/components/chart/stock-chart/data-runtime.ts +0 -1
- package/src/components/chart/stock-chart/footer.test.ts +65 -0
- package/src/components/chart/stock-chart/footer.ts +68 -13
- package/src/components/chart/stock-chart/resolved.tsx +18 -4
- package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
- package/src/components/command-bar/helpers.test.ts +1 -80
- package/src/components/command-bar/helpers.ts +1 -36
- package/src/components/command-bar/pane-settings.ts +1 -1
- package/src/components/command-bar/pane-templates/items.ts +11 -3
- package/src/components/command-bar/panel/runtime.ts +0 -2
- package/src/components/command-bar/routes/root/results.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
- package/src/components/command-bar/surface/index.test.tsx +52 -16
- package/src/components/command-bar/surface/index.tsx +0 -1
- package/src/components/command-bar/workflow/ops.ts +81 -20
- package/src/components/feed-data-table/stack-view.test.tsx +36 -130
- package/src/components/feed-data-table/stack-view.tsx +0 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/shell/index.test.tsx +2 -113
- package/src/components/layout/shell/index.tsx +0 -1
- package/src/components/layout/shell/menu.ts +0 -1
- package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
- package/src/components/onboarding/onboarding-wizard.tsx +3 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -23
- package/src/components/price-selector-dialog.tsx +3 -3
- package/src/components/ticker/badge/text.tsx +0 -1
- package/src/components/toggle-list.tsx +29 -73
- package/src/components/ui/button.tsx +4 -1
- package/src/components/ui/checkbox.tsx +76 -0
- package/src/components/ui/choice-dialog.tsx +15 -3
- package/src/components/ui/frame.tsx +1 -1
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/message-composer.tsx +4 -0
- package/src/components/ui/native-select.tsx +4 -2
- package/src/components/ui/page-stack-view.tsx +1 -1
- package/src/components/ui/ui.test.tsx +0 -78
- package/src/core/state/app/layout-reducer.ts +0 -1
- package/src/data/config/layout.ts +3 -3
- package/src/data/resource-store.ts +6 -0
- package/src/data/sqlite/cache.test.ts +5 -0
- package/src/data/ticker-store.ts +0 -7
- package/src/market-data/coordinator/auxiliary.ts +1 -44
- package/src/market-data/coordinator/entries.ts +0 -1
- package/src/market-data/coordinator/events.test.ts +49 -0
- package/src/market-data/coordinator/events.ts +25 -4
- package/src/market-data/coordinator/index.test.ts +223 -10
- package/src/market-data/coordinator/index.ts +41 -21
- package/src/market-data/coordinator/quotes.ts +13 -11
- package/src/market-data/coordinator/subscriptions.test.ts +1 -0
- package/src/market-data/hooks.tsx +17 -13
- package/src/market-data/market/freshness.ts +58 -1
- package/src/market-data/market/status.ts +19 -1
- package/src/market-data/query-store.ts +0 -4
- package/src/market-data/quotes/freshness.test.ts +49 -1
- package/src/market-data/quotes/freshness.ts +8 -0
- package/src/market-data/quotes/resolution.test.ts +185 -0
- package/src/market-data/quotes/resolution.ts +96 -10
- package/src/market-data/request-types.test.ts +61 -1
- package/src/market-data/request-types.ts +42 -8
- package/src/market-data/result-types.ts +0 -11
- package/src/market-data/selectors.ts +1 -5
- package/src/news/aggregator.ts +5 -1
- package/src/pane-settings.test.ts +0 -18
- package/src/plugins/builtin/account-management/footer.ts +2 -5
- package/src/plugins/builtin/account-management/form-components.tsx +175 -59
- package/src/plugins/builtin/account-management/keyboard.ts +32 -2
- package/src/plugins/builtin/account-management/model.test.ts +99 -4
- package/src/plugins/builtin/account-management/model.ts +259 -23
- package/src/plugins/builtin/account-management/pane.tsx +784 -155
- package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
- package/src/plugins/builtin/buildout/format.ts +2 -11
- package/src/plugins/builtin/chat/chat.test.tsx +127 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -21
- package/src/plugins/builtin/chat/content/composer-cursor.ts +29 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +22 -24
- package/src/plugins/builtin/chat/content/composer.tsx +84 -1
- package/src/plugins/builtin/chat/content/index.tsx +153 -16
- package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -1
- package/src/plugins/builtin/chat/content/mentions.test.ts +74 -0
- package/src/plugins/builtin/chat/content/mentions.ts +88 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +264 -0
- package/src/plugins/builtin/chat/content/shortcuts.ts +47 -1
- package/src/plugins/builtin/chat/content/snapshot.ts +7 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +0 -5
- package/src/plugins/builtin/chat/controller/index.ts +0 -10
- package/src/plugins/builtin/chat/controller.test.ts +5 -5
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +36 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +124 -20
- package/src/plugins/builtin/chat/sidebar.test.tsx +83 -6
- package/src/plugins/builtin/chat/sidebar.tsx +26 -2
- package/src/plugins/builtin/chat/test-harness.tsx +22 -12
- package/src/plugins/builtin/cloud/plugin.tsx +8 -1
- package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
- package/src/plugins/builtin/congress-trades/model.ts +2 -7
- package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
- package/src/plugins/builtin/correlation/index.tsx +4 -1
- package/src/plugins/builtin/correlation/relationship/controls.tsx +8 -2
- package/src/plugins/builtin/correlation/relationship/model.ts +22 -2
- package/src/plugins/builtin/correlation/relationship/pane.tsx +9 -3
- package/src/plugins/builtin/earnings/index.tsx +0 -9
- package/src/plugins/builtin/econ/index.tsx +1 -5
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/holders/pane.tsx +0 -5
- package/src/plugins/builtin/insider/index.tsx +1 -8
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
- package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -2
- package/src/plugins/builtin/news/index.tsx +0 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
- package/src/plugins/builtin/options/view.test.tsx +1 -38
- package/src/plugins/builtin/plugin-groups.ts +17 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
- package/src/plugins/builtin/portfolio-list/index.test.ts +52 -25
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
- package/src/plugins/builtin/portfolio-list/pane/data.test.ts +54 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +50 -3
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +314 -16
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -4
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +83 -24
- package/src/plugins/builtin/sec/filing-documents.ts +0 -11
- package/src/plugins/builtin/sec/index.tsx +0 -2
- package/src/plugins/builtin/substack/article-detail.tsx +1 -1
- package/src/plugins/builtin/thirteenf/api.ts +0 -15
- package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +42 -6
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +415 -64
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +23 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +8 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +3 -1
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +95 -1
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
- package/src/plugins/builtin/world-indices/index.tsx +0 -4
- package/src/plugins/builtin/world-indices/model.ts +0 -7
- package/src/plugins/ibkr/account-cache.ts +1 -1
- package/src/plugins/ibkr/gateway/market-data.ts +12 -5
- package/src/plugins/ibkr/gateway/service/index.test.ts +31 -0
- package/src/plugins/ibkr/portfolio-performance.ts +1 -1
- package/src/plugins/ibkr/trade/utils.ts +1 -7
- package/src/plugins/loader.ts +2 -1
- package/src/plugins/prediction-markets/detail.test.tsx +0 -34
- package/src/plugins/registry/context.ts +15 -0
- package/src/plugins/registry/contributions.ts +22 -11
- package/src/plugins/registry/index.test.ts +44 -0
- package/src/plugins/registry/index.ts +75 -26
- package/src/remote/controller.ts +1 -7
- package/src/remote/json-patch.ts +15 -1
- package/src/remote/server.ts +16 -6
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
- package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/bun/index.ts +42 -0
- package/src/renderers/electrobun/shared/protocol.ts +5 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +29 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
- package/src/renderers/electrobun/view/desktop/controls.tsx +135 -4
- package/src/renderers/electrobun/view/desktop/list-view.tsx +38 -25
- package/src/renderers/electrobun/view/desktop-deeplink-bridge.ts +10 -0
- package/src/renderers/electrobun/view/host/box.tsx +0 -2
- package/src/renderers/electrobun/view/host/mouse.ts +0 -11
- package/src/renderers/electrobun/view/host/scroll-box.tsx +1 -2
- package/src/renderers/electrobun/view/host/text.tsx +1 -2
- package/src/renderers/electrobun/view/http-fetch.ts +15 -2
- package/src/renderers/electrobun/view/main.tsx +3 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
- package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
- package/src/renderers/electrobun/view/ui-host.tsx +2 -0
- package/src/renderers/opentui/host.tsx +11 -3
- package/src/renderers/opentui/input-host.tsx +5 -1
- package/src/sources/gloomberb-cloud/index.test.ts +0 -72
- package/src/sources/gloomberb-cloud/index.ts +1 -30
- package/src/sources/gloomberb-cloud/news.ts +0 -12
- package/src/sources/provider-router/batches.ts +11 -3
- package/src/sources/provider-router/cached-financials.test.ts +123 -0
- package/src/sources/provider-router/financial-routes.ts +77 -5
- package/src/sources/provider-router/financials.test.ts +49 -0
- package/src/sources/provider-router/financials.ts +40 -3
- package/src/sources/provider-router/index.test.ts +314 -0
- package/src/sources/provider-router/primary.ts +17 -3
- package/src/sources/provider-router/search.ts +5 -0
- package/src/sources/provider-router/streaming.ts +6 -1
- package/src/sources/provider-router/supplemental.ts +4 -3
- package/src/sources/sec-edgar.test.ts +42 -0
- package/src/sources/sec-edgar.ts +1 -2
- package/src/sources/yahoo-finance/history.ts +5 -1
- package/src/sources/yahoo-finance/mappers.test.ts +29 -0
- package/src/sources/yahoo-finance/mappers.ts +2 -1
- package/src/sources/yahoo-finance/options.ts +59 -14
- package/src/sources/yahoo-finance/quote-summary.ts +4 -1
- package/src/sources/yahoo-finance/requests.ts +2 -1
- package/src/sources/yahoo-finance/snapshots.ts +13 -2
- package/src/sources/yahoo-finance.test.ts +40 -21
- package/src/sources/yahoo-finance.ts +19 -10
- package/src/state/app/context/index.test.tsx +0 -81
- package/src/state/app/context/selector.test.tsx +6 -2
- package/src/state/hooks/quote-streaming.test.ts +14 -1
- package/src/state/hooks/quote-streaming.ts +5 -4
- package/src/sync/controller.test.ts +31 -0
- package/src/sync/controller.ts +300 -0
- package/src/sync/core-contributors.test.ts +251 -0
- package/src/sync/core-contributors.ts +400 -0
- package/src/sync/profile-analytics.ts +39 -0
- package/src/sync/react.ts +53 -0
- package/src/sync/types.ts +77 -0
- package/src/test-support/data-provider.ts +0 -1
- package/src/types/desktop-deeplink.ts +7 -0
- package/src/types/plugin.ts +3 -0
- package/src/ui/host.tsx +15 -1
- package/src/ui/index.tsx +2 -0
- package/src/updater.test.ts +0 -21
- package/src/updater.ts +20 -16
- package/src/utils/async-deadline.ts +26 -0
- package/src/utils/exchanges.test.ts +11 -1
- package/src/utils/exchanges.ts +5 -1
- package/src/utils/selection-clipboard.ts +2 -2
- package/src/version.ts +1 -1
- package/src/components/layout/pane/header.test.tsx +0 -39
- package/src/plugins/builtin/chat/channels.test.ts +0 -28
- package/src/plugins/builtin/chat/index.tsx +0 -4
- package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
- package/src/plugins/builtin/earnings/index.test.ts +0 -12
- package/src/plugins/builtin/help/index.test.tsx +0 -242
- package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
- package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
- package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
- package/src/renderers/electrobun/view/host/text.test.ts +0 -22
- package/src/renderers/opentui/dialog-host.test.tsx +0 -44
- /package/src/{plugins/ibkr → utils}/hash.ts +0 -0
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { useCallback, useMemo, useRef, useState, type ReactNode } from "react";
|
|
2
|
-
import { Box, ChartSurface,
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
|
2
|
+
import { Box, ChartSurface, Text, TextAttributes, useNativeRenderer, type BoxRenderable } from "../../../ui";
|
|
3
3
|
import { colors } from "../../../theme/colors";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
consumeChartMouseEvent,
|
|
6
|
+
getLocalPlotPointer,
|
|
7
|
+
getRenderablePixelSize,
|
|
8
|
+
scaleLocalPixelCoordinate,
|
|
9
|
+
type ChartMouseEvent,
|
|
10
|
+
type LocalPlotPointer,
|
|
11
|
+
} from "../core/pointer";
|
|
5
12
|
import type { NativeChartBitmap } from "../native/chart-rasterizer";
|
|
6
13
|
import { truncateWithEllipsis } from "../../../utils/text-wrap";
|
|
7
14
|
import { useStaticChartBitmapSize } from "./chart/bitmap";
|
|
@@ -12,11 +19,36 @@ import {
|
|
|
12
19
|
renderBarChartYAxis,
|
|
13
20
|
renderNativeBarChart,
|
|
14
21
|
resolveBarChartHover,
|
|
22
|
+
resolveNativeBarChartHover,
|
|
15
23
|
type BarChartColors,
|
|
16
24
|
type BarChartHover,
|
|
25
|
+
type BarChartScene,
|
|
17
26
|
type BarChartSeries,
|
|
18
27
|
} from "../bar-chart-renderer";
|
|
19
28
|
|
|
29
|
+
export interface BarChartSurfaceHoverGeometry {
|
|
30
|
+
bitmapPixelWidth: number;
|
|
31
|
+
renderablePixelWidth?: number | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveBarChartSurfaceHover(
|
|
35
|
+
scene: BarChartScene,
|
|
36
|
+
pointer: Pick<LocalPlotPointer, "cellX" | "pixelX">,
|
|
37
|
+
geometry?: BarChartSurfaceHoverGeometry | null,
|
|
38
|
+
): BarChartHover | null {
|
|
39
|
+
if (geometry && pointer.pixelX !== null) {
|
|
40
|
+
const pixelX = scaleLocalPixelCoordinate(
|
|
41
|
+
pointer.pixelX,
|
|
42
|
+
geometry.renderablePixelWidth ?? geometry.bitmapPixelWidth,
|
|
43
|
+
geometry.bitmapPixelWidth,
|
|
44
|
+
);
|
|
45
|
+
if (pixelX !== null) {
|
|
46
|
+
return resolveNativeBarChartHover(scene, pixelX, geometry.bitmapPixelWidth);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return resolveBarChartHover(scene, pointer.cellX);
|
|
50
|
+
}
|
|
51
|
+
|
|
20
52
|
export interface StaticBarChartSurfaceProps {
|
|
21
53
|
series: BarChartSeries[];
|
|
22
54
|
width: number;
|
|
@@ -25,6 +57,8 @@ export interface StaticBarChartSurfaceProps {
|
|
|
25
57
|
title?: string;
|
|
26
58
|
header?: ReactNode;
|
|
27
59
|
formatValue?: (value: number) => string;
|
|
60
|
+
hiddenSeriesIds?: readonly string[] | ReadonlySet<string>;
|
|
61
|
+
onToggleSeries?: (seriesId: string) => void;
|
|
28
62
|
onHoverChange?: (hover: BarChartHover | null) => void;
|
|
29
63
|
onMouseDown?: (event: ChartMouseEvent) => void;
|
|
30
64
|
}
|
|
@@ -36,6 +70,8 @@ export function StaticBarChartSurface({
|
|
|
36
70
|
title,
|
|
37
71
|
header,
|
|
38
72
|
formatValue,
|
|
73
|
+
hiddenSeriesIds,
|
|
74
|
+
onToggleSeries,
|
|
39
75
|
onHoverChange,
|
|
40
76
|
onMouseDown,
|
|
41
77
|
colors: chartColors,
|
|
@@ -59,11 +95,18 @@ export function StaticBarChartSurface({
|
|
|
59
95
|
negativeColor: chartColors?.negativeColor ?? colors.negative,
|
|
60
96
|
hoverColor: chartColors?.hoverColor ?? colors.textBright,
|
|
61
97
|
}), [chartColors]);
|
|
62
|
-
const
|
|
98
|
+
const hiddenSeriesIdSet = useMemo(() => (
|
|
99
|
+
hiddenSeriesIds instanceof Set ? hiddenSeriesIds : new Set(hiddenSeriesIds ?? [])
|
|
100
|
+
), [hiddenSeriesIds]);
|
|
101
|
+
const visibleSeries = useMemo(
|
|
102
|
+
() => series.filter((item) => !hiddenSeriesIdSet.has(item.id)),
|
|
103
|
+
[hiddenSeriesIdSet, series],
|
|
104
|
+
);
|
|
105
|
+
const scene = useMemo(() => buildBarChartScene(visibleSeries, {
|
|
63
106
|
width: plotWidth,
|
|
64
107
|
height: plotHeight,
|
|
65
108
|
colors: resolvedChartColors,
|
|
66
|
-
}), [plotHeight, plotWidth, resolvedChartColors,
|
|
109
|
+
}), [plotHeight, plotWidth, resolvedChartColors, visibleSeries]);
|
|
67
110
|
|
|
68
111
|
const bitmapSize = useStaticChartBitmapSize(plotWidth, plotHeight);
|
|
69
112
|
|
|
@@ -78,8 +121,8 @@ export function StaticBarChartSurface({
|
|
|
78
121
|
const hoverLabel = useMemo(() => {
|
|
79
122
|
if (!hover) return null;
|
|
80
123
|
const value = formatValue ? formatValue(hover.value) : String(hover.value);
|
|
81
|
-
return
|
|
82
|
-
}, [formatValue, hover,
|
|
124
|
+
return visibleSeries.length > 1 ? `${hover.category} ${hover.seriesLabel}: ${value}` : `${hover.category}: ${value}`;
|
|
125
|
+
}, [formatValue, hover, visibleSeries.length]);
|
|
83
126
|
const readout = useMemo(() => {
|
|
84
127
|
if (!hoverLabel) return title ?? "";
|
|
85
128
|
return title ? `${title} ${hoverLabel}` : hoverLabel;
|
|
@@ -98,6 +141,17 @@ export function StaticBarChartSurface({
|
|
|
98
141
|
return { labelLeft, labelTop, labelWidth, text };
|
|
99
142
|
}, [hover, hoverLabel, plotHeight, plotWidth]);
|
|
100
143
|
|
|
144
|
+
const resolvePointerHover = useCallback((pointer: LocalPlotPointer): BarChartHover | null => {
|
|
145
|
+
if (!scene) return null;
|
|
146
|
+
const renderablePixelSize = bitmapSize ? getRenderablePixelSize(plotRef.current, renderer) : null;
|
|
147
|
+
return resolveBarChartSurfaceHover(scene, pointer, bitmapSize
|
|
148
|
+
? {
|
|
149
|
+
bitmapPixelWidth: bitmapSize.pixelWidth,
|
|
150
|
+
renderablePixelWidth: renderablePixelSize?.pixelWidth ?? bitmapSize.pixelWidth,
|
|
151
|
+
}
|
|
152
|
+
: null);
|
|
153
|
+
}, [bitmapSize, renderer, scene]);
|
|
154
|
+
|
|
101
155
|
const handleMouseMove = useCallback((event: ChartMouseEvent) => {
|
|
102
156
|
if (!scene) return;
|
|
103
157
|
const pointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
@@ -106,11 +160,11 @@ export function StaticBarChartSurface({
|
|
|
106
160
|
onHoverChange?.(null);
|
|
107
161
|
return;
|
|
108
162
|
}
|
|
109
|
-
const nextHover =
|
|
163
|
+
const nextHover = resolvePointerHover(pointer);
|
|
110
164
|
setHover(nextHover);
|
|
111
165
|
onHoverChange?.(nextHover);
|
|
112
166
|
consumeChartMouseEvent(event);
|
|
113
|
-
}, [onHoverChange, renderer, scene]);
|
|
167
|
+
}, [onHoverChange, renderer, resolvePointerHover, scene]);
|
|
114
168
|
|
|
115
169
|
const handleMouseOut = useCallback(() => {
|
|
116
170
|
setHover(null);
|
|
@@ -122,16 +176,54 @@ export function StaticBarChartSurface({
|
|
|
122
176
|
if (!scene) return;
|
|
123
177
|
const pointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
124
178
|
if (!pointer) return;
|
|
125
|
-
const nextHover =
|
|
179
|
+
const nextHover = resolvePointerHover(pointer);
|
|
126
180
|
setHover(nextHover);
|
|
127
181
|
onHoverChange?.(nextHover);
|
|
128
182
|
consumeChartMouseEvent(event);
|
|
129
|
-
}, [onHoverChange, onMouseDown, renderer, scene]);
|
|
183
|
+
}, [onHoverChange, onMouseDown, renderer, resolvePointerHover, scene]);
|
|
184
|
+
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (hover && hiddenSeriesIdSet.has(hover.seriesId)) {
|
|
187
|
+
setHover(null);
|
|
188
|
+
onHoverChange?.(null);
|
|
189
|
+
}
|
|
190
|
+
}, [hiddenSeriesIdSet, hover, onHoverChange]);
|
|
191
|
+
|
|
192
|
+
const legend = legendRows ? (
|
|
193
|
+
<Box flexDirection="row" height={1}>
|
|
194
|
+
{series.map((item, index) => {
|
|
195
|
+
const hidden = hiddenSeriesIdSet.has(item.id);
|
|
196
|
+
const legendColor = hidden ? colors.textDim : item.color;
|
|
197
|
+
const legendAttributes = hidden ? TextAttributes.STRIKETHROUGH | TextAttributes.DIM : TextAttributes.NONE;
|
|
198
|
+
const handleLegendMouseDown = onToggleSeries
|
|
199
|
+
? (event: any) => {
|
|
200
|
+
event.stopPropagation?.();
|
|
201
|
+
onToggleSeries(item.id);
|
|
202
|
+
}
|
|
203
|
+
: undefined;
|
|
204
|
+
return (
|
|
205
|
+
<Box
|
|
206
|
+
key={item.id}
|
|
207
|
+
flexDirection="row"
|
|
208
|
+
height={1}
|
|
209
|
+
onMouseDown={handleLegendMouseDown}
|
|
210
|
+
cursor={onToggleSeries ? "pointer" : undefined}
|
|
211
|
+
data-gloom-interactive={onToggleSeries ? "true" : undefined}
|
|
212
|
+
>
|
|
213
|
+
<Text>{index > 0 ? " " : " "}</Text>
|
|
214
|
+
<Text fg={legendColor} bg={legendColor} attributes={legendAttributes}> </Text>
|
|
215
|
+
<Text fg={legendColor} attributes={legendAttributes}>{` ${item.label}`}</Text>
|
|
216
|
+
</Box>
|
|
217
|
+
);
|
|
218
|
+
})}
|
|
219
|
+
</Box>
|
|
220
|
+
) : null;
|
|
130
221
|
|
|
131
222
|
if (!scene) {
|
|
132
223
|
return (
|
|
133
224
|
<Box flexDirection="column" width={totalWidth} height={totalHeight} onMouseDown={onMouseDown}>
|
|
134
225
|
{header ?? (title ? <Text fg={colors.textBright} bold>{truncateWithEllipsis(title, totalWidth)}</Text> : null)}
|
|
226
|
+
{legend}
|
|
135
227
|
<Text fg={colors.textDim}>No chart data</Text>
|
|
136
228
|
</Box>
|
|
137
229
|
);
|
|
@@ -146,15 +238,7 @@ export function StaticBarChartSurface({
|
|
|
146
238
|
</Text>
|
|
147
239
|
)
|
|
148
240
|
) : null}
|
|
149
|
-
{
|
|
150
|
-
<Text>
|
|
151
|
-
{series.map((item, index) => (
|
|
152
|
-
<Span key={item.id} fg={item.color}>
|
|
153
|
-
{index > 0 ? " " : ""}{"■ "}{item.label}
|
|
154
|
-
</Span>
|
|
155
|
-
))}
|
|
156
|
-
</Text>
|
|
157
|
-
) : null}
|
|
241
|
+
{legend}
|
|
158
242
|
<Box flexDirection="row" width={totalWidth} height={plotHeight}>
|
|
159
243
|
{yAxisWidth ? (
|
|
160
244
|
<Box flexDirection="column" width={yAxisWidth} height={plotHeight}>
|
|
@@ -3,7 +3,6 @@ import type { InstrumentRef } from "../../../market-data/request-types";
|
|
|
3
3
|
import type { DataProvider } from "../../../types/data-provider";
|
|
4
4
|
import type { PricePoint, TickerFinancials } from "../../../types/financials";
|
|
5
5
|
import { appendLiveQuotePoint, resolveStableOhlcProjectionOptions } from "../core/data";
|
|
6
|
-
import type { DateWindowRange } from "../core/controller";
|
|
7
6
|
import {
|
|
8
7
|
getChartResolutionLabel,
|
|
9
8
|
sortChartResolutions,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { ProjectedChartPoint } from "../core/data";
|
|
3
|
+
import { resolveStockChartFooterOhlcReadout } from "./footer";
|
|
4
|
+
|
|
5
|
+
function point(
|
|
6
|
+
close: number,
|
|
7
|
+
overrides: Partial<ProjectedChartPoint> = {},
|
|
8
|
+
): ProjectedChartPoint {
|
|
9
|
+
return {
|
|
10
|
+
date: new Date("2026-07-03T10:00:00Z"),
|
|
11
|
+
open: close,
|
|
12
|
+
high: close,
|
|
13
|
+
low: close,
|
|
14
|
+
close,
|
|
15
|
+
volume: 100,
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("stock chart footer OHLC readout", () => {
|
|
21
|
+
test("summarizes the visible OHLC window for idle chart readouts", () => {
|
|
22
|
+
const readout = resolveStockChartFooterOhlcReadout({
|
|
23
|
+
activePoint: null,
|
|
24
|
+
hasDisplayCursor: false,
|
|
25
|
+
points: [
|
|
26
|
+
point(100, { open: 99, high: 101, low: 98, volume: 10 }),
|
|
27
|
+
point(104, { open: 100, high: 108, low: 99, volume: 20 }),
|
|
28
|
+
point(106, { open: 104, high: 107, low: 103, volume: 30 }),
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(readout).toMatchObject({
|
|
33
|
+
open: 99,
|
|
34
|
+
high: 108,
|
|
35
|
+
low: 98,
|
|
36
|
+
close: 106,
|
|
37
|
+
volume: 60,
|
|
38
|
+
});
|
|
39
|
+
expect(readout?.changePercent).toBeCloseTo(((106 - 99) / 99) * 100);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("uses the summary only when there is no chart cursor", () => {
|
|
43
|
+
const activePoint = point(104, { open: 103, high: 105, low: 102 });
|
|
44
|
+
const points = [
|
|
45
|
+
point(100, { open: 99, high: 101, low: 98 }),
|
|
46
|
+
point(106, { open: 104, high: 107, low: 103 }),
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const idleReadout = resolveStockChartFooterOhlcReadout({
|
|
50
|
+
activePoint,
|
|
51
|
+
hasDisplayCursor: false,
|
|
52
|
+
points,
|
|
53
|
+
});
|
|
54
|
+
const cursorReadout = resolveStockChartFooterOhlcReadout({
|
|
55
|
+
activePoint,
|
|
56
|
+
hasDisplayCursor: true,
|
|
57
|
+
points,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(idleReadout?.close).toBe(106);
|
|
61
|
+
expect(idleReadout?.changePercent).not.toBeNull();
|
|
62
|
+
expect(cursorReadout).toMatchObject(activePoint);
|
|
63
|
+
expect(cursorReadout?.changePercent).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useMemo, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
|
|
2
|
-
import { formatCompact } from "../../../utils/format";
|
|
2
|
+
import { formatCompact, formatPercentRaw } from "../../../utils/format";
|
|
3
3
|
import { formatMarketPriceWithCurrency } from "../../../market-data/market/format";
|
|
4
4
|
import { usePaneFooter, type PaneFooterSegment, type PaneHint } from "../../layout/pane/footer";
|
|
5
|
+
import { priceColor } from "../../../theme/colors";
|
|
5
6
|
import type { PricePoint } from "../../../types/financials";
|
|
6
7
|
import type { ProjectedChartPoint } from "../core/data";
|
|
7
8
|
import type { DateWindowRange } from "../core/controller";
|
|
@@ -24,8 +25,53 @@ import {
|
|
|
24
25
|
type StockChartViewportState,
|
|
25
26
|
} from "./viewport";
|
|
26
27
|
|
|
27
|
-
interface
|
|
28
|
+
interface StockChartOhlcReadout extends ProjectedChartPoint {
|
|
29
|
+
changePercent: number | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function summarizeOhlcWindow(points: readonly ProjectedChartPoint[]): StockChartOhlcReadout | null {
|
|
33
|
+
const first = points[0];
|
|
34
|
+
const last = points.at(-1);
|
|
35
|
+
if (!first || !last) return null;
|
|
36
|
+
|
|
37
|
+
let high = first.high;
|
|
38
|
+
let low = first.low;
|
|
39
|
+
let volume = 0;
|
|
40
|
+
for (const point of points) {
|
|
41
|
+
high = Math.max(high, point.high);
|
|
42
|
+
low = Math.min(low, point.low);
|
|
43
|
+
volume += point.volume;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
date: last.date,
|
|
48
|
+
open: first.open,
|
|
49
|
+
high,
|
|
50
|
+
low,
|
|
51
|
+
close: last.close,
|
|
52
|
+
volume,
|
|
53
|
+
changePercent: Number.isFinite(first.open) && first.open !== 0
|
|
54
|
+
? ((last.close - first.open) / first.open) * 100
|
|
55
|
+
: null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveStockChartFooterOhlcReadout({
|
|
60
|
+
activePoint,
|
|
61
|
+
hasDisplayCursor,
|
|
62
|
+
points,
|
|
63
|
+
}: {
|
|
28
64
|
activePoint: ProjectedChartPoint | null;
|
|
65
|
+
hasDisplayCursor: boolean;
|
|
66
|
+
points: readonly ProjectedChartPoint[];
|
|
67
|
+
}): StockChartOhlcReadout | null {
|
|
68
|
+
if (hasDisplayCursor) {
|
|
69
|
+
return activePoint ? { ...activePoint, changePercent: null } : null;
|
|
70
|
+
}
|
|
71
|
+
return summarizeOhlcWindow(points);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface StockChartFooterOptions {
|
|
29
75
|
activePreset: TimeRange | null;
|
|
30
76
|
baseDateBounds: DateWindowRange | null;
|
|
31
77
|
boundsHistoryDates: Date[];
|
|
@@ -37,6 +83,7 @@ interface StockChartFooterOptions {
|
|
|
37
83
|
history: PricePoint[];
|
|
38
84
|
manualMinimumSpanMs: number | null;
|
|
39
85
|
navigableDateWindow: DateWindowRange | null;
|
|
86
|
+
ohlcReadout: StockChartOhlcReadout | null;
|
|
40
87
|
pendingAutoWindowRef: MutableRefObject<DateWindowRange | null>;
|
|
41
88
|
pendingCanonicalResetRef: MutableRefObject<number>;
|
|
42
89
|
persistRenderMode: (mode: ChartRenderMode) => void;
|
|
@@ -50,7 +97,6 @@ interface StockChartFooterOptions {
|
|
|
50
97
|
setRenderedAutoView: Dispatch<SetStateAction<AutoRenderedView | null>>;
|
|
51
98
|
setResolution: (resolution: ChartResolution) => void;
|
|
52
99
|
setViewState: Dispatch<SetStateAction<StockChartViewportState>>;
|
|
53
|
-
showOhlcSummary: boolean;
|
|
54
100
|
updateDisplayCursorTarget: (next: DisplayCursorState, motionKind: ChartCursorMotionKind) => void;
|
|
55
101
|
visibleDateWindow: DateWindowRange | null;
|
|
56
102
|
visiblePriceRange: number | undefined;
|
|
@@ -58,7 +104,6 @@ interface StockChartFooterOptions {
|
|
|
58
104
|
}
|
|
59
105
|
|
|
60
106
|
export function useStockChartFooter({
|
|
61
|
-
activePoint,
|
|
62
107
|
activePreset,
|
|
63
108
|
baseDateBounds,
|
|
64
109
|
boundsHistoryDates,
|
|
@@ -70,6 +115,7 @@ export function useStockChartFooter({
|
|
|
70
115
|
history,
|
|
71
116
|
manualMinimumSpanMs,
|
|
72
117
|
navigableDateWindow,
|
|
118
|
+
ohlcReadout,
|
|
73
119
|
pendingAutoWindowRef,
|
|
74
120
|
pendingCanonicalResetRef,
|
|
75
121
|
persistRenderMode,
|
|
@@ -83,7 +129,6 @@ export function useStockChartFooter({
|
|
|
83
129
|
setRenderedAutoView,
|
|
84
130
|
setResolution,
|
|
85
131
|
setViewState,
|
|
86
|
-
showOhlcSummary,
|
|
87
132
|
updateDisplayCursorTarget,
|
|
88
133
|
visibleDateWindow,
|
|
89
134
|
visiblePriceRange,
|
|
@@ -196,7 +241,7 @@ export function useStockChartFooter({
|
|
|
196
241
|
]);
|
|
197
242
|
|
|
198
243
|
const chartFooterInfo = useMemo<PaneFooterSegment[]>(() => {
|
|
199
|
-
if (compact || !
|
|
244
|
+
if (compact || !ohlcReadout) return [];
|
|
200
245
|
|
|
201
246
|
const formatPrice = (value: number) => formatMarketPriceWithCurrency(value, chartCurrency, {
|
|
202
247
|
assetCategory: chartAssetCategory,
|
|
@@ -210,16 +255,27 @@ export function useStockChartFooter({
|
|
|
210
255
|
? []
|
|
211
256
|
: [
|
|
212
257
|
{ text: "O", tone: "label" as const },
|
|
213
|
-
{ text: formatPrice(
|
|
258
|
+
{ text: formatPrice(ohlcReadout.open), tone: "value" as const },
|
|
214
259
|
]),
|
|
215
260
|
{ text: "H", tone: "label" },
|
|
216
|
-
{ text: formatPrice(
|
|
261
|
+
{ text: formatPrice(ohlcReadout.high), tone: "value" },
|
|
217
262
|
{ text: "L", tone: "label" },
|
|
218
|
-
{ text: formatPrice(
|
|
263
|
+
{ text: formatPrice(ohlcReadout.low), tone: "value" },
|
|
219
264
|
{ text: "C", tone: "label" },
|
|
220
|
-
{ text: formatPrice(
|
|
265
|
+
{ text: formatPrice(ohlcReadout.close), tone: "value" },
|
|
266
|
+
...(ohlcReadout.changePercent === null
|
|
267
|
+
? []
|
|
268
|
+
: [
|
|
269
|
+
{ text: "%", tone: "label" as const },
|
|
270
|
+
{
|
|
271
|
+
text: formatPercentRaw(ohlcReadout.changePercent),
|
|
272
|
+
tone: "value" as const,
|
|
273
|
+
color: priceColor(ohlcReadout.changePercent),
|
|
274
|
+
bold: true,
|
|
275
|
+
},
|
|
276
|
+
]),
|
|
221
277
|
{ text: "V", tone: "label" },
|
|
222
|
-
{ text: formatCompact(
|
|
278
|
+
{ text: formatCompact(ohlcReadout.volume), tone: "value" },
|
|
223
279
|
];
|
|
224
280
|
|
|
225
281
|
return [{
|
|
@@ -227,12 +283,11 @@ export function useStockChartFooter({
|
|
|
227
283
|
parts,
|
|
228
284
|
}];
|
|
229
285
|
}, [
|
|
230
|
-
activePoint,
|
|
231
286
|
chartAssetCategory,
|
|
232
287
|
chartCurrency,
|
|
233
288
|
compact,
|
|
289
|
+
ohlcReadout,
|
|
234
290
|
projectionMode,
|
|
235
|
-
showOhlcSummary,
|
|
236
291
|
visiblePriceRange,
|
|
237
292
|
]);
|
|
238
293
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { memo, type ComponentProps } from "react";
|
|
2
|
-
import { useStockChartFooter } from "./footer";
|
|
1
|
+
import { memo, useMemo, type ComponentProps } from "react";
|
|
2
|
+
import { resolveStockChartFooterOhlcReadout, useStockChartFooter } from "./footer";
|
|
3
3
|
import { useStockChartKeyboardShortcuts } from "./keyboard";
|
|
4
4
|
import { useStockChartControlRenderInvalidation } from "./rendering/invalidation";
|
|
5
5
|
import { useStockChartRenderOutput } from "./rendering/output";
|
|
@@ -139,9 +139,23 @@ function useResolvedStockChartControls(
|
|
|
139
139
|
} = runtime;
|
|
140
140
|
const { output, status } = render;
|
|
141
141
|
const manualMinimumSpanMs = getChartResolutionStepMs(dataRuntime.renderedResolution);
|
|
142
|
+
const ohlcReadout = useMemo(
|
|
143
|
+
() => output.showOhlcSummary
|
|
144
|
+
? resolveStockChartFooterOhlcReadout({
|
|
145
|
+
activePoint: output.activePoint,
|
|
146
|
+
hasDisplayCursor: output.hasDisplayCursor,
|
|
147
|
+
points: projectionModel.projection.points,
|
|
148
|
+
})
|
|
149
|
+
: null,
|
|
150
|
+
[
|
|
151
|
+
output.activePoint,
|
|
152
|
+
output.hasDisplayCursor,
|
|
153
|
+
output.showOhlcSummary,
|
|
154
|
+
projectionModel.projection.points,
|
|
155
|
+
],
|
|
156
|
+
);
|
|
142
157
|
|
|
143
158
|
useStockChartFooter({
|
|
144
|
-
activePoint: output.activePoint,
|
|
145
159
|
activePreset: viewportRuntime.activePreset,
|
|
146
160
|
baseDateBounds: dataRuntime.baseDateBounds,
|
|
147
161
|
boundsHistoryDates: dataRuntime.boundsHistoryDates,
|
|
@@ -153,6 +167,7 @@ function useResolvedStockChartControls(
|
|
|
153
167
|
history: dataRuntime.history,
|
|
154
168
|
manualMinimumSpanMs,
|
|
155
169
|
navigableDateWindow: dataRuntime.navigableDateWindow,
|
|
170
|
+
ohlcReadout,
|
|
156
171
|
pendingAutoWindowRef: interactionRuntime.pendingAutoWindowRef,
|
|
157
172
|
pendingCanonicalResetRef: interactionRuntime.pendingCanonicalResetRef,
|
|
158
173
|
persistRenderMode: viewportRuntime.persistRenderMode,
|
|
@@ -166,7 +181,6 @@ function useResolvedStockChartControls(
|
|
|
166
181
|
setRenderedAutoView: dataRuntime.setRenderedAutoView,
|
|
167
182
|
setResolution: viewportRuntime.setResolution,
|
|
168
183
|
setViewState: settings.setViewState,
|
|
169
|
-
showOhlcSummary: output.showOhlcSummary,
|
|
170
184
|
updateDisplayCursorTarget: interactionRuntime.updateDisplayCursorTarget,
|
|
171
185
|
visibleDateWindow: dataRuntime.visibleDateWindow,
|
|
172
186
|
visiblePriceRange: output.visiblePriceRange,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useEffect, useRef, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
|
|
2
|
-
import { applyBufferedPanExpansion } from "../../core/scroll";
|
|
3
2
|
import {
|
|
4
3
|
applyDateWindowViewport,
|
|
5
4
|
buildVisibleDateWindow,
|
|
@@ -15,7 +14,6 @@ import {
|
|
|
15
14
|
type StoredChartSelectionSyncState,
|
|
16
15
|
} from "../../core/pane-settings";
|
|
17
16
|
import {
|
|
18
|
-
getNextBufferRange,
|
|
19
17
|
getSupportMaxRange,
|
|
20
18
|
type ManualChartResolution,
|
|
21
19
|
} from "../../core/resolution";
|
|
@@ -31,7 +29,6 @@ import type {
|
|
|
31
29
|
import type { ChartCursorMotionKind } from "../../cursor-motion";
|
|
32
30
|
import type { AutoRenderedView } from "../auto";
|
|
33
31
|
import {
|
|
34
|
-
clearAutoViewportState,
|
|
35
32
|
resolveViewportPresetSelection,
|
|
36
33
|
resolveViewportStoredSelection,
|
|
37
34
|
type PendingExpansionAction,
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { PaneTemplateDef, WizardStep } from "../../types/plugin";
|
|
3
|
-
import type {
|
|
4
|
-
CommandBarRoute,
|
|
5
|
-
CommandBarWorkflowField,
|
|
6
|
-
} from "./workflow/types";
|
|
3
|
+
import type { CommandBarWorkflowField } from "./workflow/types";
|
|
7
4
|
import {
|
|
8
5
|
buildGeneratedTemplateField,
|
|
9
6
|
getCollectionCommandAction,
|
|
10
7
|
getCollectionCommandKind,
|
|
11
8
|
getCollectionCommandVerb,
|
|
12
9
|
getFirstVisibleFieldId,
|
|
13
|
-
getScreenFooterLeft,
|
|
14
|
-
getScreenFooterRight,
|
|
15
10
|
getVisibleWorkflowFields,
|
|
16
11
|
isCollectionCommand,
|
|
17
|
-
isRootParsedCommand,
|
|
18
12
|
isRouteCommandId,
|
|
19
13
|
isWorkflowTextField,
|
|
20
14
|
looksDestructiveCommand,
|
|
@@ -22,7 +16,6 @@ import {
|
|
|
22
16
|
routeCommandIdToScreen,
|
|
23
17
|
slugifyName,
|
|
24
18
|
summarizeError,
|
|
25
|
-
summarizePaneSettingValue,
|
|
26
19
|
summarizeWorkflowFieldValue,
|
|
27
20
|
} from "./helpers";
|
|
28
21
|
|
|
@@ -79,26 +72,6 @@ describe("command-bar helpers", () => {
|
|
|
79
72
|
}, ["symbol", "price", "pnl", "volume"])).toBe("Symbol, Price, P&L +1");
|
|
80
73
|
});
|
|
81
74
|
|
|
82
|
-
test("summarizes pane setting values consistently", () => {
|
|
83
|
-
expect(summarizePaneSettingValue({
|
|
84
|
-
key: "theme",
|
|
85
|
-
label: "Theme",
|
|
86
|
-
type: "select",
|
|
87
|
-
options: [{ label: "Nord", value: "nord" }],
|
|
88
|
-
}, "nord")).toBe("Nord");
|
|
89
|
-
expect(summarizePaneSettingValue({
|
|
90
|
-
key: "columns",
|
|
91
|
-
label: "Columns",
|
|
92
|
-
type: "ordered-multi-select",
|
|
93
|
-
options: [
|
|
94
|
-
{ label: "Symbol", value: "symbol" },
|
|
95
|
-
{ label: "Price", value: "price" },
|
|
96
|
-
{ label: "P&L", value: "pnl" },
|
|
97
|
-
{ label: "Volume", value: "volume" },
|
|
98
|
-
],
|
|
99
|
-
}, ["symbol", "price", "pnl", "volume"])).toBe("Symbol, Price, P&L +1");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
75
|
test("normalizes wizard fields and derives defaults from steps", () => {
|
|
103
76
|
const steps: WizardStep[] = [
|
|
104
77
|
{ key: "intro", label: "Intro", type: "info", body: ["Line one", "Line two"] },
|
|
@@ -157,61 +130,9 @@ describe("command-bar helpers", () => {
|
|
|
157
130
|
});
|
|
158
131
|
});
|
|
159
132
|
|
|
160
|
-
test("formats screen footers based on route context", () => {
|
|
161
|
-
const orderedPickerRoute: CommandBarRoute = {
|
|
162
|
-
kind: "picker",
|
|
163
|
-
pickerId: "field-multi-select",
|
|
164
|
-
title: "Columns",
|
|
165
|
-
query: "",
|
|
166
|
-
selectedIdx: 0,
|
|
167
|
-
hoveredIdx: null,
|
|
168
|
-
options: [],
|
|
169
|
-
payload: { fieldType: "ordered-multi-select" },
|
|
170
|
-
};
|
|
171
|
-
const filteredPickerRoute: CommandBarRoute = {
|
|
172
|
-
...orderedPickerRoute,
|
|
173
|
-
query: "vol",
|
|
174
|
-
};
|
|
175
|
-
const workflowRoute: CommandBarRoute = {
|
|
176
|
-
kind: "workflow",
|
|
177
|
-
workflowId: "create-pane",
|
|
178
|
-
title: "Create Pane",
|
|
179
|
-
fields: [],
|
|
180
|
-
values: {},
|
|
181
|
-
activeFieldId: null,
|
|
182
|
-
submitLabel: "Create",
|
|
183
|
-
pending: false,
|
|
184
|
-
error: null,
|
|
185
|
-
payload: {
|
|
186
|
-
kind: "builtin",
|
|
187
|
-
actionId: "create-pane",
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
const confirmRoute: CommandBarRoute = {
|
|
191
|
-
kind: "confirm",
|
|
192
|
-
confirmId: "delete",
|
|
193
|
-
title: "Delete",
|
|
194
|
-
body: ["Delete item?"],
|
|
195
|
-
confirmLabel: "Delete",
|
|
196
|
-
cancelLabel: "Back",
|
|
197
|
-
tone: "danger",
|
|
198
|
-
onConfirm: () => {},
|
|
199
|
-
pending: false,
|
|
200
|
-
error: null,
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
expect(getScreenFooterLeft(null)).toBe("up/down move enter select");
|
|
204
|
-
expect(getScreenFooterLeft(orderedPickerRoute)).toBe("up/down move space toggle [ ] reorder enter done");
|
|
205
|
-
expect(getScreenFooterRight(orderedPickerRoute)).toBe("backspace/esc back");
|
|
206
|
-
expect(getScreenFooterRight(filteredPickerRoute)).toBe("backspace delete esc back");
|
|
207
|
-
expect(getScreenFooterRight(workflowRoute)).toBe("backspace/esc back");
|
|
208
|
-
expect(getScreenFooterRight(confirmRoute)).toBe("backspace/esc back");
|
|
209
|
-
});
|
|
210
|
-
|
|
211
133
|
test("maps route and collection command ids", () => {
|
|
212
134
|
expect(isRouteCommandId("plugins")).toBe(true);
|
|
213
135
|
expect(routeCommandIdToScreen("plugins")).toBe("plugins");
|
|
214
|
-
expect(isRootParsedCommand("remove-portfolio")).toBe(true);
|
|
215
136
|
expect(isCollectionCommand("remove-portfolio")).toBe(true);
|
|
216
137
|
expect(getCollectionCommandKind("remove-portfolio")).toBe("portfolio");
|
|
217
138
|
expect(getCollectionCommandAction("remove-portfolio")).toBe("remove");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CommandDef
|
|
1
|
+
import type { CommandDef } from "../../types/plugin";
|
|
2
2
|
import type { CommandBarRoute } from "./workflow/types";
|
|
3
3
|
import type { CollectionKind, CollectionMembershipAction } from "./workflow/ops";
|
|
4
4
|
|
|
@@ -37,14 +37,6 @@ export function routeCommandIdToScreen(commandId: RouteCommandId): "ticker-searc
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function isRootParsedCommand(commandId: string): boolean {
|
|
41
|
-
return commandId === "security-description"
|
|
42
|
-
|| commandId === "add-watchlist"
|
|
43
|
-
|| commandId === "add-portfolio"
|
|
44
|
-
|| commandId === "remove-watchlist"
|
|
45
|
-
|| commandId === "remove-portfolio";
|
|
46
|
-
}
|
|
47
|
-
|
|
48
40
|
export function isCollectionCommand(commandId: string): commandId is CollectionCommandId {
|
|
49
41
|
return commandId === "add-watchlist"
|
|
50
42
|
|| commandId === "add-portfolio"
|
|
@@ -64,33 +56,6 @@ export function getCollectionCommandVerb(action: CollectionMembershipAction): st
|
|
|
64
56
|
return action === "add" ? "Add" : "Remove";
|
|
65
57
|
}
|
|
66
58
|
|
|
67
|
-
export function summarizePaneSettingValue(field: PaneSettingField, value: unknown): string {
|
|
68
|
-
switch (field.type) {
|
|
69
|
-
case "toggle":
|
|
70
|
-
return value === true ? "On" : "Off";
|
|
71
|
-
case "text":
|
|
72
|
-
return typeof value === "string" && value.trim().length > 0 ? value : "Unset";
|
|
73
|
-
case "select": {
|
|
74
|
-
const option = field.options.find((entry) => entry.value === value);
|
|
75
|
-
return option?.label ?? "Unset";
|
|
76
|
-
}
|
|
77
|
-
case "multi-select":
|
|
78
|
-
case "ordered-multi-select": {
|
|
79
|
-
const selected = Array.isArray(value)
|
|
80
|
-
? value.filter((entry): entry is string => typeof entry === "string")
|
|
81
|
-
: [];
|
|
82
|
-
if (selected.length === 0) return "None";
|
|
83
|
-
const labels = selected
|
|
84
|
-
.map((entry) => field.options.find((option) => option.value === entry)?.label ?? entry)
|
|
85
|
-
.slice(0, 3);
|
|
86
|
-
const suffix = selected.length > 3 ? ` +${selected.length - 3}` : "";
|
|
87
|
-
return `${labels.join(", ")}${suffix}`;
|
|
88
|
-
}
|
|
89
|
-
default:
|
|
90
|
-
return "";
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
59
|
export function getScreenFooterLeft(route: CommandBarRoute | null): string {
|
|
95
60
|
if (!route) return "up/down move enter select";
|
|
96
61
|
switch (route.kind) {
|