gloomberb 0.11.2 → 0.12.0
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 +7 -4
- package/README.zh-CN.md +4 -2
- package/package.json +28 -1
- package/src/api-client/auth.ts +10 -0
- package/src/api-client/data.ts +108 -1
- package/src/api-client/index.test.ts +127 -0
- package/src/api-client/index.ts +134 -1
- package/src/api-client/normalizers.ts +39 -0
- package/src/api-client/paths.ts +85 -1
- package/src/api-client/types.ts +220 -0
- package/src/app/pane-runtime/index.tsx +15 -12
- package/src/app/pane-runtime/layout-placement.test.ts +27 -0
- package/src/app/pane-runtime/layout-placement.ts +13 -0
- package/src/app/pane-runtime/plugin-bindings.ts +62 -0
- package/src/app.tsx +60 -18
- package/src/architecture/browser-entry-graph.test.ts +96 -0
- package/src/brokers/profile-form.test.ts +25 -27
- package/src/brokers/test-broker.ts +68 -0
- package/src/capabilities/chart-series.ts +3 -2
- package/src/cli/commands/market.ts +2 -2
- package/src/cli/commands/plugins.ts +39 -11
- package/src/cli/desktop-pane-shot.ts +285 -16
- package/src/cli/index.ts +1 -1
- package/src/cli/pane-functions/capabilities.ts +61 -31
- package/src/cli/pane-functions/catalog.ts +16 -4
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/headless.test.ts +247 -0
- package/src/cli/pane-functions/headless.ts +423 -0
- package/src/cli/pane-functions/index.test.ts +9 -0
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +27 -2
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +26 -8
- package/src/cli/pane-functions/screenshot.test.ts +76 -0
- package/src/cli/pane-functions/screenshot.ts +236 -20
- package/src/cli/registry.ts +0 -22
- package/src/cli/restore-plugins.ts +34 -0
- package/src/cli/result.ts +7 -10
- package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
- package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
- package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
- package/src/components/chart/composite/composite-chart.test.tsx +68 -20
- package/src/components/chart/composite/composite-chart.tsx +301 -204
- package/src/components/chart/composite/format.ts +8 -2
- package/src/components/chart/composite/interactions.test.ts +68 -102
- package/src/components/chart/composite/interactions.ts +214 -264
- package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
- package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
- package/src/components/chart/composite/scene.ts +57 -3
- package/src/components/chart/composite/text-renderer.ts +3 -2
- package/src/components/chart/composite/time-scale.ts +138 -57
- package/src/components/chart/composite/types.ts +25 -1
- package/src/components/chart/core/data.ts +9 -13
- package/src/components/chart/core/date-window.ts +7 -1
- package/src/components/chart/core/pointer.ts +3 -0
- package/src/components/chart/core/types.ts +0 -45
- package/src/components/chart/native/chart-rasterizer.ts +0 -1
- package/src/components/chart/native/kitty/index.test.ts +0 -188
- package/src/components/chart/native/raster/crosshair.ts +6 -5
- package/src/components/chart/native/raster/primitives.ts +0 -23
- package/src/components/chart/native/renderer-selection.ts +81 -31
- package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
- package/src/components/chart/static/chart-surface.tsx +150 -0
- package/src/components/chart/static/index.ts +8 -0
- package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
- package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
- package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
- package/src/components/chart/static/series.ts +49 -0
- package/src/components/command-bar/action-runtime.ts +1 -3
- package/src/components/command-bar/assist/model.test.ts +17 -2
- package/src/components/command-bar/assist/model.ts +40 -9
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/commands/direct/runtime.ts +1 -1
- package/src/components/command-bar/commands/registry.ts +0 -11
- package/src/components/command-bar/helpers.test.ts +2 -2
- package/src/components/command-bar/helpers.ts +2 -6
- package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
- package/src/components/command-bar/list/badge.test.ts +54 -0
- package/src/components/command-bar/list/badge.ts +62 -0
- package/src/components/command-bar/list/model.test.ts +103 -0
- package/src/components/command-bar/list/model.ts +61 -4
- package/src/components/command-bar/list/view.test.tsx +113 -0
- package/src/components/command-bar/list/view.tsx +147 -131
- package/src/components/command-bar/pane-templates/workflow.ts +1 -1
- package/src/components/command-bar/panel/body.tsx +51 -65
- package/src/components/command-bar/panel/index.tsx +48 -13
- package/src/components/command-bar/panel/layout.test.ts +139 -0
- package/src/components/command-bar/panel/layout.ts +65 -28
- package/src/components/command-bar/panel/native-surface.ts +24 -0
- package/src/components/command-bar/panel/prompt-binding.ts +54 -0
- package/src/components/command-bar/panel/runtime.ts +6 -3
- package/src/components/command-bar/panel/state.ts +41 -7
- package/src/components/command-bar/panel/types.ts +7 -1
- package/src/components/command-bar/routes/root/provider-search.ts +34 -15
- package/src/components/command-bar/routes/root/results.test.ts +161 -0
- package/src/components/command-bar/routes/root/results.ts +30 -28
- package/src/components/command-bar/routes/root/runtime.ts +37 -20
- package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
- package/src/components/command-bar/routes/root/search-providers.ts +205 -0
- package/src/components/command-bar/routes/root/selection.ts +1 -12
- package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
- package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
- package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
- package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
- package/src/components/command-bar/routing/actions.ts +1 -81
- package/src/components/command-bar/routing/launch-request.ts +1 -1
- package/src/components/command-bar/routing/list-state.ts +6 -21
- package/src/components/command-bar/selection-runtime.ts +1 -1
- package/src/components/command-bar/surface/assist.test.tsx +14 -10
- package/src/components/command-bar/surface/environment.ts +8 -1
- package/src/components/command-bar/surface/index.test.tsx +84 -68
- package/src/components/command-bar/surface/index.tsx +33 -36
- package/src/components/command-bar/surface/test-harness.tsx +15 -3
- package/src/components/command-bar/theme-picker.tsx +43 -15
- package/src/components/command-bar/view-model.test.ts +53 -8
- package/src/components/command-bar/view-model.ts +43 -14
- package/src/components/command-bar/workflow/tickers.ts +1 -0
- package/src/components/command-bar/workflow/types.ts +1 -1
- package/src/components/data-table/export.test.ts +21 -0
- package/src/components/data-table/export.ts +21 -0
- package/src/components/data-table/view.test.tsx +45 -6
- package/src/components/data-table/view.tsx +55 -9
- package/src/components/index.ts +10 -2
- package/src/components/input-search-bar.test.tsx +36 -2
- package/src/components/input-search-bar.tsx +18 -1
- package/src/components/layout/header.test.tsx +57 -10
- package/src/components/layout/header.tsx +292 -149
- package/src/components/layout/market-summary.test.ts +81 -0
- package/src/components/layout/market-summary.ts +109 -0
- package/src/components/layout/shell/chrome.ts +96 -1
- package/src/components/layout/shell/index.tsx +11 -3
- package/src/components/layout/shell/menu.ts +9 -0
- package/src/components/layout/shell/pane/actions.ts +29 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/shortcuts.ts +3 -0
- package/src/components/layout/status-bar.test.tsx +9 -8
- package/src/components/layout/status-bar.tsx +85 -54
- package/src/components/onboarding/onboarding-frame.tsx +2 -26
- package/src/components/price-sparkline/index.ts +61 -34
- package/src/components/price-sparkline/view.tsx +1 -1
- package/src/components/sign-in-gate.test.tsx +128 -0
- package/src/components/sign-in-gate.tsx +149 -0
- package/src/components/speedometer-gauge/desktop.tsx +2 -12
- package/src/components/ticker/badge/list.tsx +17 -3
- package/src/components/ticker/list-table-view.tsx +6 -3
- package/src/components/ui/data-table/index.tsx +22 -4
- package/src/components/ui/data-table/opentui/index.tsx +165 -120
- package/src/components/ui/frame.tsx +28 -0
- package/src/components/ui/index.ts +3 -0
- package/src/components/ui/native-select.tsx +20 -10
- package/src/components/ui/select-button.tsx +138 -0
- package/src/components/ui/status.tsx +2 -2
- package/src/components/ui/table-layout.ts +1 -1
- package/src/components/use-double-click-activation.ts +7 -3
- package/src/core/app-services.ts +3 -3
- package/src/data/config/chart-settings.ts +4 -4
- package/src/data/config/layout.ts +12 -6
- package/src/data/config/store/index.test.ts +97 -0
- package/src/data/config/store/migrations.ts +41 -0
- package/src/data/config/store/normalize.ts +5 -3
- package/src/i18n/es.ts +6 -2
- package/src/i18n/ja.ts +6 -2
- package/src/i18n/ko.ts +6 -2
- package/src/i18n/zh-cn.ts +6 -2
- package/src/i18n/zh-tw.ts +6 -2
- package/src/layout-marketplace/gallery-terminal.tsx +126 -96
- package/src/layout-marketplace/gallery.test.tsx +65 -1
- package/src/layout-marketplace/pane-imagery.ts +1 -0
- package/src/layout-marketplace/payload.test.ts +17 -0
- package/src/layout-marketplace/payload.ts +40 -5
- package/src/market-data/coordinator/index.ts +3 -2
- package/src/market-data/coordinator/quotes.ts +173 -66
- package/src/market-data/coordinator/subscriptions.test.ts +215 -37
- package/src/pane-settings.ts +1 -1
- package/src/plugins/builtin/analytics/pane-model.ts +1 -1
- package/src/plugins/builtin/analytics/view.tsx +1 -1
- package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
- package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
- package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
- package/src/plugins/builtin/chart-composer/settings.ts +2 -13
- package/src/plugins/builtin/chat/content/composer.tsx +8 -4
- package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
- package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -0
- package/src/plugins/builtin/composite-plugins.ts +16 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
- package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
- package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
- package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings/index.tsx +1 -0
- package/src/plugins/builtin/earnings-calls/data.ts +223 -0
- package/src/plugins/builtin/earnings-calls/format.ts +49 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
- package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
- package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
- package/src/plugins/builtin/econ/detail-view.tsx +1 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
- package/src/plugins/builtin/econ/index.tsx +3 -1
- package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
- package/src/plugins/builtin/econ-statistics/client.ts +92 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
- package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
- package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
- package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
- package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
- package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
- package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
- package/src/plugins/builtin/econ-statistics/view.ts +90 -0
- package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
- package/src/plugins/builtin/fear-greed/data.ts +6 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
- package/src/plugins/builtin/help/index.tsx +4 -0
- package/src/plugins/builtin/holders/index.tsx +1 -0
- package/src/plugins/builtin/insider/index.tsx +1 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +1 -0
- package/src/plugins/builtin/market-valuation/align.ts +122 -0
- package/src/plugins/builtin/market-valuation/cache.ts +32 -0
- package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
- package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
- package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
- package/src/plugins/builtin/market-valuation/client.ts +116 -0
- package/src/plugins/builtin/market-valuation/defs.ts +251 -0
- package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
- package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
- package/src/plugins/builtin/market-valuation/headless.ts +170 -0
- package/src/plugins/builtin/market-valuation/index.tsx +64 -0
- package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
- package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
- package/src/plugins/builtin/market-valuation/model.ts +8 -0
- package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
- package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
- package/src/plugins/builtin/market-valuation/series.ts +12 -0
- package/src/plugins/builtin/market-valuation/settings.ts +38 -0
- package/src/plugins/builtin/market-valuation/sources.ts +130 -0
- package/src/plugins/builtin/market-valuation/trend.ts +89 -0
- package/src/plugins/builtin/market-valuation/view.ts +137 -0
- package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
- package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
- package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
- package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
- package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
- package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
- package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
- package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
- package/src/plugins/builtin/news/wire/index.tsx +31 -44
- package/src/plugins/builtin/options/analytics.test.ts +83 -0
- package/src/plugins/builtin/options/analytics.ts +110 -0
- package/src/plugins/builtin/options/index.tsx +18 -2
- package/src/plugins/builtin/options/table.ts +121 -51
- package/src/plugins/builtin/options/types.ts +22 -15
- package/src/plugins/builtin/options/view.test.tsx +53 -1
- package/src/plugins/builtin/options/view.tsx +92 -41
- package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
- package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
- package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
- package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
- package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
- package/src/plugins/builtin/plugin-module.test.ts +2 -4
- package/src/plugins/builtin/plugin-module.ts +9 -8
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
- package/src/plugins/builtin/research/index.tsx +4 -0
- package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
- package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
- package/src/plugins/builtin/research-search/data.ts +109 -0
- package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
- package/src/plugins/builtin/research-search/document-view.tsx +254 -0
- package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
- package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
- package/src/plugins/builtin/research-search/index.tsx +85 -0
- package/src/plugins/builtin/research-search/model.test.ts +40 -0
- package/src/plugins/builtin/research-search/model.ts +291 -0
- package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
- package/src/plugins/builtin/research-search/pane.tsx +678 -0
- package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
- package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
- package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
- package/src/plugins/builtin/research-search/snippet.ts +134 -0
- package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
- package/src/plugins/builtin/scanner/flow-model.ts +29 -1
- package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
- package/src/plugins/builtin/scanner/index.ts +2 -0
- package/src/plugins/builtin/sec/index.tsx +1 -0
- package/src/plugins/builtin/sectors/index.tsx +1 -0
- package/src/plugins/builtin/shared/series-cache.ts +93 -0
- package/src/plugins/builtin/short-interest/index.tsx +1 -0
- package/src/plugins/builtin/short-interest/pane.tsx +1 -1
- package/src/plugins/builtin/thirteenf/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
- package/src/plugins/builtin/volatility/index.tsx +1 -1
- package/src/plugins/builtin/world-indices/index.tsx +1 -0
- package/src/plugins/builtin/world-venue-map/index.ts +27 -0
- package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
- package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
- package/src/plugins/builtin/world-venue-map/model.ts +268 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
- package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/bundle.test.ts +109 -0
- package/src/plugins/bundle.ts +139 -0
- package/src/plugins/catalog-backend.ts +6 -44
- package/src/plugins/catalog-browser.test.ts +1 -0
- package/src/plugins/catalog-browser.ts +12 -0
- package/src/plugins/catalog-manifest.test.ts +43 -0
- package/src/plugins/catalog-manifest.ts +92 -0
- package/src/plugins/catalog-ui.ts +20 -4
- package/src/plugins/catalog.ts +9 -2
- package/src/plugins/current-target.ts +28 -0
- package/src/plugins/host-contract.ts +37 -0
- package/src/plugins/host-link.ts +158 -0
- package/src/plugins/host-modules.ts +61 -0
- package/src/plugins/loader.ts +41 -22
- package/src/plugins/ownership.ts +1 -0
- package/src/plugins/prediction-markets/index.tsx +1 -0
- package/src/plugins/registry/context.ts +3 -0
- package/src/plugins/registry/contributions.ts +27 -0
- package/src/plugins/registry/index.test.ts +25 -0
- package/src/plugins/registry/index.ts +8 -0
- package/src/plugins/registry/pane-settings.test.ts +53 -0
- package/src/plugins/registry/pane-settings.ts +29 -3
- package/src/plugins/seed.test.ts +83 -0
- package/src/plugins/seed.ts +80 -0
- package/src/public/broker.ts +31 -0
- package/src/public/market-data.ts +9 -0
- package/src/public/pane-hooks.ts +57 -0
- package/src/public/public-api.test.ts +285 -0
- package/src/public/react.ts +62 -0
- package/src/public/test-support.ts +23 -0
- package/src/public/time-series.ts +8 -0
- package/src/public/utils.ts +77 -0
- package/src/remote/layout-helpers.ts +6 -8
- package/src/remote/resources.ts +2 -0
- package/src/remote/schema.ts +1 -0
- package/src/remote/semantic-tree.test.tsx +105 -0
- package/src/remote/semantic-tree.tsx +53 -35
- package/src/renderers/browser/main.tsx +6 -0
- package/src/renderers/browser/ui-host.tsx +11 -0
- package/src/renderers/cloudflare/worker.test.ts +19 -0
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
- package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
- package/src/renderers/electrobun/bun/index.ts +6 -2
- package/src/renderers/electrobun/shared/protocol.ts +25 -0
- package/src/renderers/electrobun/view/build-assets.ts +0 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
- package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
- package/src/renderers/electrobun/view/external-plugins.ts +63 -0
- package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
- package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
- package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
- package/src/renderers/electrobun/view/host/mouse.ts +16 -2
- package/src/renderers/electrobun/view/main.tsx +27 -2
- package/src/renderers/electrobun/view/styles.css +5 -0
- package/src/renderers/electrobun/view/toast-host.tsx +18 -0
- package/src/renderers/electrobun/view/ui-host.tsx +3 -0
- package/src/renderers/opentui/chart-surface.tsx +5 -1
- package/src/renderers/opentui/host.tsx +20 -2
- package/src/renderers/opentui/interaction-performance.test.ts +79 -0
- package/src/renderers/opentui/interaction-performance.ts +188 -0
- package/src/renderers/opentui/start.tsx +26 -1
- package/src/renderers/opentui/test-utils.tsx +13 -0
- package/src/renderers/opentui/toast-host.tsx +26 -2
- package/src/renderers/opentui/ui-host.tsx +1 -0
- package/src/sources/provider-router/search.test.ts +191 -0
- package/src/sources/provider-router/search.ts +92 -39
- package/src/state/app/context/index.tsx +2 -0
- package/src/state/app/context/selector.test.tsx +33 -1
- package/src/state/hooks/quote-streaming.test.tsx +93 -0
- package/src/state/hooks/quote-streaming.ts +106 -30
- package/src/state/pane-table-export-registry.ts +50 -0
- package/src/theme/color-utils.ts +1 -1
- package/src/theme/colors.ts +50 -0
- package/src/theme/themes.test.ts +15 -2
- package/src/theme/themes.ts +19 -1
- package/src/tickers/search/index.test.ts +26 -0
- package/src/tickers/search/index.ts +42 -17
- package/src/tickers/search/ranking.ts +14 -0
- package/src/time-series/chart-data.test.ts +216 -0
- package/src/time-series/range.ts +12 -0
- package/src/time-series/resolution.test.ts +25 -26
- package/src/time-series/resolution.ts +44 -25
- package/src/time-series/resolve.ts +7 -0
- package/src/time-series/spec.ts +5 -5
- package/src/time-series/types.ts +3 -1
- package/src/time-series/use-chart-resolution.ts +5 -0
- package/src/types/config.ts +123 -3
- package/src/types/data-provider.ts +11 -0
- package/src/types/headless.ts +161 -0
- package/src/types/plugin.ts +104 -20
- package/src/ui/host.tsx +19 -1
- package/src/ui/index.tsx +1 -0
- package/src/ui/toast.tsx +8 -4
- package/src/utils/csv.test.ts +13 -0
- package/src/utils/csv.ts +37 -0
- package/src/utils/format.ts +35 -0
- package/src/utils/fuzzy-search.test.ts +94 -0
- package/src/utils/fuzzy-search.ts +81 -22
- package/src/utils/save-text-file.test.ts +24 -0
- package/src/utils/save-text-file.ts +36 -0
- package/src/utils/shortcut-labels.ts +14 -0
- package/src/components/chart/core/axis-format.ts +0 -157
- package/src/components/chart/core/index.test.ts +0 -1075
- package/src/components/chart/core/projection.ts +0 -357
- package/src/components/chart/core/render-utils.ts +0 -4
- package/src/components/chart/core/renderer.ts +0 -244
- package/src/components/chart/core/scene.ts +0 -180
- package/src/components/chart/core/time-axis.ts +0 -330
- package/src/components/chart/cursor-motion.ts +0 -47
- package/src/components/chart/indicators/types.ts +0 -26
- package/src/components/chart/market-session.ts +0 -176
- package/src/components/chart/multi-line/renderer.test.ts +0 -83
- package/src/components/chart/multi-line/renderer.ts +0 -161
- package/src/components/chart/multi-line/scene.ts +0 -188
- package/src/components/chart/native/raster/layout.ts +0 -129
- package/src/components/chart/native/raster/price-chart.ts +0 -244
- package/src/components/chart/rendering/chart-draw.ts +0 -270
- package/src/components/chart/rendering/geometry.ts +0 -95
- package/src/components/chart/rendering/pixel-buffer.ts +0 -267
- package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
- package/src/components/chart/scatter-chart-renderer.ts +0 -223
- package/src/components/chart/static/chart/surface.tsx +0 -358
- package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
- package/src/components/command-bar/routes/root/article-results.ts +0 -55
- package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
- package/src/plugins/builtin/news/wire/article-search.ts +0 -140
- package/src/plugins/builtin/substack/api/auth.ts +0 -185
- package/src/plugins/builtin/substack/api/cache.ts +0 -82
- package/src/plugins/builtin/substack/api/loaders.ts +0 -170
- package/src/plugins/builtin/substack/api/store.ts +0 -186
- package/src/plugins/builtin/substack/api/types.ts +0 -42
- package/src/plugins/builtin/substack/api.test.ts +0 -298
- package/src/plugins/builtin/substack/article-detail.tsx +0 -397
- package/src/plugins/builtin/substack/article-stack.tsx +0 -133
- package/src/plugins/builtin/substack/content.ts +0 -478
- package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
- package/src/plugins/builtin/substack/index.tsx +0 -49
- package/src/plugins/builtin/substack/login-view.tsx +0 -126
- package/src/plugins/builtin/substack/model.test.ts +0 -155
- package/src/plugins/builtin/substack/normalize.ts +0 -343
- package/src/plugins/builtin/substack/pane-footer.ts +0 -52
- package/src/plugins/builtin/substack/pane-state.ts +0 -170
- package/src/plugins/builtin/substack/pane.tsx +0 -547
- package/src/plugins/builtin/substack/read-state.ts +0 -30
- package/src/plugins/builtin/substack/table.ts +0 -111
- package/src/plugins/builtin/substack/types.ts +0 -71
- package/src/plugins/builtin/substack/utils.ts +0 -131
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
- package/src/plugins/ibkr/account-cache.test.ts +0 -102
- package/src/plugins/ibkr/account-cache.ts +0 -24
- package/src/plugins/ibkr/broker-adapter.ts +0 -286
- package/src/plugins/ibkr/config.test.ts +0 -79
- package/src/plugins/ibkr/config.ts +0 -250
- package/src/plugins/ibkr/dialogs.tsx +0 -40
- package/src/plugins/ibkr/flex/index.test.ts +0 -264
- package/src/plugins/ibkr/flex/index.ts +0 -367
- package/src/plugins/ibkr/flex/statement-client.ts +0 -235
- package/src/plugins/ibkr/fundamental-parser.ts +0 -149
- package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
- package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
- package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
- package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
- package/src/plugins/ibkr/gateway/connection.ts +0 -152
- package/src/plugins/ibkr/gateway/contracts.ts +0 -132
- package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
- package/src/plugins/ibkr/gateway/helpers.ts +0 -33
- package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
- package/src/plugins/ibkr/gateway/history.ts +0 -120
- package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
- package/src/plugins/ibkr/gateway/market-data.ts +0 -345
- package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
- package/src/plugins/ibkr/gateway/orders.ts +0 -63
- package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
- package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
- package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
- package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
- package/src/plugins/ibkr/gateway/selection.ts +0 -51
- package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
- package/src/plugins/ibkr/gateway/service/index.ts +0 -429
- package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
- package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
- package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
- package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
- package/src/plugins/ibkr/gateway/service/native.ts +0 -543
- package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
- package/src/plugins/ibkr/gateway/types.ts +0 -27
- package/src/plugins/ibkr/index.test.tsx +0 -162
- package/src/plugins/ibkr/index.tsx +0 -159
- package/src/plugins/ibkr/instance-selection.test.ts +0 -120
- package/src/plugins/ibkr/instance-selection.ts +0 -36
- package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
- package/src/plugins/ibkr/portfolio-performance.ts +0 -129
- package/src/plugins/ibkr/trade/badge.tsx +0 -28
- package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
- package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
- package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
- package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
- package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
- package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
- package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
- package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
- package/src/plugins/ibkr/trade/tab/model.ts +0 -159
- package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
- package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
- package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
- package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
- package/src/plugins/ibkr/trade/utils.test.ts +0 -175
- package/src/plugins/ibkr/trade/utils.ts +0 -144
- package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
- package/src/plugins/ibkr/trading/pane.tsx +0 -332
- package/src/plugins/ibkr/trading/state.test.ts +0 -109
- package/src/plugins/ibkr/trading/state.ts +0 -335
|
@@ -21,7 +21,7 @@ export function dedupeById<T extends { id: string }>(items: T[]): T[] {
|
|
|
21
21
|
return result;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export type CommandBarMode = "default" | "search" | "themes" | "
|
|
24
|
+
export type CommandBarMode = "default" | "search" | "themes" | "layout" | "direct-command";
|
|
25
25
|
|
|
26
26
|
export interface CommandBarModeInfo {
|
|
27
27
|
kind: CommandBarMode;
|
|
@@ -36,6 +36,14 @@ export interface CommandBarSection<T> {
|
|
|
36
36
|
|
|
37
37
|
export type CommandBarSectionOrder = "default" | "app-first" | "ranked";
|
|
38
38
|
|
|
39
|
+
/** Sort positions contributed by plugin search providers, keyed by section heading. */
|
|
40
|
+
export type CommandBarCategoryPriorities = ReadonlyMap<string, number>;
|
|
41
|
+
|
|
42
|
+
export interface CommandBarSectionOptions {
|
|
43
|
+
sectionOrder?: CommandBarSectionOrder;
|
|
44
|
+
categoryPriorities?: CommandBarCategoryPriorities;
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
export interface CommandBarItemView {
|
|
40
48
|
id: string;
|
|
41
49
|
label: string;
|
|
@@ -74,8 +82,6 @@ export function resolveCommandBarMode(query: string, commandList?: Command[]): C
|
|
|
74
82
|
return { kind: "search", badge: match.prefix, hint: t("Open security details for a ticker") };
|
|
75
83
|
case "theme":
|
|
76
84
|
return { kind: "themes", badge: "THEMES", hint: t("Preview with arrows, Enter to save, Esc to revert") };
|
|
77
|
-
case "plugins":
|
|
78
|
-
return { kind: "plugins", badge: "PLUGINS", hint: t("Toggle plugins without leaving the list") };
|
|
79
85
|
case "layout":
|
|
80
86
|
return { kind: "layout", badge: "LAYOUT", hint: t("Organize panes, history, and saved layouts") };
|
|
81
87
|
default:
|
|
@@ -85,9 +91,10 @@ export function resolveCommandBarMode(query: string, commandList?: Command[]): C
|
|
|
85
91
|
|
|
86
92
|
/**
|
|
87
93
|
* A section whose every row is disabled or unselectable is an offer, not an
|
|
88
|
-
* answer, so it sorts below real matches however its category is prioritized
|
|
94
|
+
* answer, so it sorts below real matches however its category is prioritized:
|
|
95
|
+
* past the async bands (100 to 200) and the AI's lead, short of danger (900).
|
|
89
96
|
*/
|
|
90
|
-
const OFFER_SECTION_DEMOTION =
|
|
97
|
+
const OFFER_SECTION_DEMOTION = 500;
|
|
91
98
|
|
|
92
99
|
interface SectionSortableItem {
|
|
93
100
|
category: string;
|
|
@@ -102,7 +109,7 @@ function isOfferOnlySection<T extends SectionSortableItem>(items: T[]): boolean
|
|
|
102
109
|
|
|
103
110
|
export function buildSections<T extends SectionSortableItem>(
|
|
104
111
|
items: T[],
|
|
105
|
-
options?:
|
|
112
|
+
options?: CommandBarSectionOptions,
|
|
106
113
|
): Array<CommandBarSection<T>> {
|
|
107
114
|
const sections: Array<CommandBarSection<T>> = [];
|
|
108
115
|
for (const item of items) {
|
|
@@ -116,9 +123,9 @@ export function buildSections<T extends SectionSortableItem>(
|
|
|
116
123
|
return sections
|
|
117
124
|
.map((section, index) => ({ section, index }))
|
|
118
125
|
.sort((a, b) => {
|
|
119
|
-
const leftPriority = getCategoryPriority(a.section.category, options
|
|
126
|
+
const leftPriority = getCategoryPriority(a.section.category, options)
|
|
120
127
|
+ (isOfferOnlySection(a.section.items) ? OFFER_SECTION_DEMOTION : 0);
|
|
121
|
-
const rightPriority = getCategoryPriority(b.section.category, options
|
|
128
|
+
const rightPriority = getCategoryPriority(b.section.category, options)
|
|
122
129
|
+ (isOfferOnlySection(b.section.items) ? OFFER_SECTION_DEMOTION : 0);
|
|
123
130
|
const priorityDiff = leftPriority - rightPriority;
|
|
124
131
|
return priorityDiff !== 0 ? priorityDiff : a.index - b.index;
|
|
@@ -133,8 +140,6 @@ export function getEmptyState(mode: CommandBarMode, query: string, searchQuery?:
|
|
|
133
140
|
return { label: t("Type a ticker symbol"), detail: t("Open security details after resolving a ticker") };
|
|
134
141
|
}
|
|
135
142
|
return { label: tf('No matches for "{query}"', { query: searchQuery }), detail: t("Try a symbol, company name, or exchange variant") };
|
|
136
|
-
case "plugins":
|
|
137
|
-
return { label: t("No plugins match"), detail: query.trim() || t("Toggleable plugins will appear here") };
|
|
138
143
|
case "themes":
|
|
139
144
|
return { label: t("No themes match"), detail: query.trim() || t("Installed themes will appear here") };
|
|
140
145
|
case "layout":
|
|
@@ -172,12 +177,36 @@ export function truncateText(text: string, width: number): string {
|
|
|
172
177
|
return truncateToDisplayWidth(text, width);
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
|
|
180
|
+
/**
|
|
181
|
+
* An exactly matching symbol is the most certain answer the terminal has, so
|
|
182
|
+
* nothing outranks it. Typing "sive" put the AI's "DES SIVE" above the SIVE row
|
|
183
|
+
* it was derived from, which is a guess sitting above the fact behind it.
|
|
184
|
+
*/
|
|
185
|
+
const EXACT_MATCH_SECTION_PRIORITY = -150;
|
|
186
|
+
/**
|
|
187
|
+
* The AI leads the rest even though it is the slowest source (~600ms+): it
|
|
188
|
+
* translates the sentence the user typed into commands, which is the answer to
|
|
189
|
+
* what they asked when no symbol matched outright. Its Thinking placeholder
|
|
190
|
+
* reserves the rows from the start, so the arrival replaces a row instead of
|
|
191
|
+
* shifting the list.
|
|
192
|
+
*/
|
|
193
|
+
const ASSIST_SECTION_PRIORITY = -100;
|
|
194
|
+
/**
|
|
195
|
+
* The other async sections sit below the local matches in arrival order, so
|
|
196
|
+
* each answer only ever pushes rows below itself: instruments at 100, then news
|
|
197
|
+
* at 190 and the rest of the corpus at 200 (both contributed by their provider).
|
|
198
|
+
*/
|
|
199
|
+
const INSTRUMENTS_SECTION_PRIORITY = 100;
|
|
200
|
+
|
|
201
|
+
function getCategoryPriority(category: string, options?: CommandBarSectionOptions): number {
|
|
202
|
+
const contributed = options?.categoryPriorities?.get(category);
|
|
203
|
+
if (contributed !== undefined) return contributed;
|
|
204
|
+
const sectionOrder = options?.sectionOrder ?? "default";
|
|
176
205
|
const normalized = category.trim().toLowerCase();
|
|
177
206
|
if (sectionOrder === "ranked") return 0;
|
|
178
|
-
|
|
179
|
-
if (normalized === "
|
|
180
|
-
if (normalized === "
|
|
207
|
+
if (normalized === "ask ai") return ASSIST_SECTION_PRIORITY;
|
|
208
|
+
if (normalized === "exact match") return EXACT_MATCH_SECTION_PRIORITY;
|
|
209
|
+
if (normalized === "instruments") return INSTRUMENTS_SECTION_PRIORITY;
|
|
181
210
|
if (sectionOrder === "app-first") {
|
|
182
211
|
if (normalized === "saved") return 100;
|
|
183
212
|
if (normalized === "primary listing") return 110;
|
|
@@ -36,6 +36,7 @@ function getTickerSearchContext(state: AppState, collectionId: string | null) {
|
|
|
36
36
|
const activePortfolio = state.config.portfolios.find((portfolio) => portfolio.id === collectionId);
|
|
37
37
|
return {
|
|
38
38
|
preferBroker: true,
|
|
39
|
+
interactive: true,
|
|
39
40
|
brokerId: activePortfolio?.brokerId,
|
|
40
41
|
brokerInstanceId: activePortfolio?.brokerInstanceId,
|
|
41
42
|
};
|
|
@@ -39,7 +39,7 @@ interface CommandBarRouteBase {
|
|
|
39
39
|
|
|
40
40
|
interface CommandBarModeRoute extends CommandBarRouteBase {
|
|
41
41
|
kind: "mode";
|
|
42
|
-
screen: "ticker-search" | "
|
|
42
|
+
screen: "ticker-search" | "layout";
|
|
43
43
|
query: string;
|
|
44
44
|
selectedIdx: number;
|
|
45
45
|
hoveredIdx: number | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { EXCEL_CSV_BOM } from "../../utils/csv";
|
|
3
|
+
import { createDataTableCsv } from "./export";
|
|
4
|
+
|
|
5
|
+
test("exports displayed table cells and skips section headers", () => {
|
|
6
|
+
const items = [
|
|
7
|
+
{ type: "section", name: "Americas", value: "" },
|
|
8
|
+
{ type: "row", name: "S&P 500", value: "+1.25%" },
|
|
9
|
+
];
|
|
10
|
+
const csv = createDataTableCsv({
|
|
11
|
+
columns: [
|
|
12
|
+
{ id: "name", label: "Name", width: 20, align: "left" },
|
|
13
|
+
{ id: "value", label: "Change", width: 10, align: "right" },
|
|
14
|
+
],
|
|
15
|
+
items,
|
|
16
|
+
renderSectionHeader: (item) => item.type === "section" ? { text: item.name } : null,
|
|
17
|
+
renderCell: (item, column) => ({ text: item[column.id as "name" | "value"] }),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(csv).toBe(`${EXCEL_CSV_BOM}Name,Change\nS&P 500,'+1.25%`);
|
|
21
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DataTableColumn, DataTableProps } from "../ui";
|
|
2
|
+
import { serializeCsv } from "../../utils/csv";
|
|
3
|
+
|
|
4
|
+
export function createDataTableCsv<
|
|
5
|
+
T,
|
|
6
|
+
C extends DataTableColumn = DataTableColumn,
|
|
7
|
+
>({
|
|
8
|
+
columns,
|
|
9
|
+
items,
|
|
10
|
+
renderCell,
|
|
11
|
+
renderSectionHeader,
|
|
12
|
+
}: Pick<DataTableProps<T, C>, "columns" | "items" | "renderCell" | "renderSectionHeader">): string {
|
|
13
|
+
const rows = items.flatMap((item, index) => {
|
|
14
|
+
if (renderSectionHeader?.(item, index)) return [];
|
|
15
|
+
return [columns.map((column) => renderCell(item, column, index, { selected: false }).text)];
|
|
16
|
+
});
|
|
17
|
+
return serializeCsv([
|
|
18
|
+
columns.map((column) => column.label),
|
|
19
|
+
...rows,
|
|
20
|
+
], { excelCompatible: true });
|
|
21
|
+
}
|
|
@@ -43,7 +43,7 @@ afterEach(async () => {
|
|
|
43
43
|
testSetup = undefined;
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
function Harness() {
|
|
46
|
+
function Harness({ onCursor = () => {} }: { onCursor?: () => void }) {
|
|
47
47
|
const [selectedIndex, setSelectedIndex] = useState(1);
|
|
48
48
|
const [cursorIndex, setCursorIndex] = useState(1);
|
|
49
49
|
const [activatedTitle, setActivatedTitle] = useState("");
|
|
@@ -64,7 +64,10 @@ function Harness() {
|
|
|
64
64
|
selectedIndex,
|
|
65
65
|
onChange: (index) => setSelectedIndex(index),
|
|
66
66
|
}}
|
|
67
|
-
onCursorChange={(_row, index) =>
|
|
67
|
+
onCursorChange={(_row, index) => {
|
|
68
|
+
onCursor();
|
|
69
|
+
setCursorIndex(index);
|
|
70
|
+
}}
|
|
68
71
|
onActivate={(row) => {
|
|
69
72
|
if (row.type === "row") setActivatedTitle(row.title);
|
|
70
73
|
}}
|
|
@@ -122,6 +125,7 @@ function LargeSelectionHarness({
|
|
|
122
125
|
return { text: row.title + (index === 500 ? "" : "") };
|
|
123
126
|
}}
|
|
124
127
|
emptyStateTitle="No rows"
|
|
128
|
+
scrollToIndex={500}
|
|
125
129
|
/>
|
|
126
130
|
</PaneInstanceProvider>
|
|
127
131
|
</AppContext>
|
|
@@ -228,6 +232,20 @@ describe("DataTableView", () => {
|
|
|
228
232
|
expect(testSetup.captureCharFrame()).toContain("cursor=Second row selected=First row activated=First row");
|
|
229
233
|
});
|
|
230
234
|
|
|
235
|
+
test("does no cursor or scroll work when navigation is already at an edge", async () => {
|
|
236
|
+
let cursorChanges = 0;
|
|
237
|
+
testSetup = await testRender(
|
|
238
|
+
<Harness onCursor={() => { cursorChanges += 1; }} />,
|
|
239
|
+
{ width: 60, height: 12 },
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
await renderSettled();
|
|
243
|
+
await emitKeypress({ name: "up", sequence: "\u001B[A" });
|
|
244
|
+
await renderSettled();
|
|
245
|
+
|
|
246
|
+
expect(cursorChanges).toBe(0);
|
|
247
|
+
});
|
|
248
|
+
|
|
231
249
|
test("keeps selection current across repeated keypresses before the next render", async () => {
|
|
232
250
|
testSetup = await testRender(<Harness />, { width: 60, height: 12 });
|
|
233
251
|
|
|
@@ -242,19 +260,40 @@ describe("DataTableView", () => {
|
|
|
242
260
|
expect(testSetup.captureCharFrame()).toContain("selected=Third row activated=Third row");
|
|
243
261
|
});
|
|
244
262
|
|
|
245
|
-
test("
|
|
246
|
-
|
|
263
|
+
test("keeps the immediate cursor visible while a controlled selection is deferred", async () => {
|
|
264
|
+
testSetup = await testRender(
|
|
265
|
+
<LargeSelectionHarness onIsSelected={() => {}} />,
|
|
266
|
+
{ width: 60, height: 12 },
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
await renderSettled();
|
|
270
|
+
await emitKeypressBatch(Array.from({ length: 30 }, () => ({
|
|
271
|
+
name: "down",
|
|
272
|
+
sequence: "\u001B[B",
|
|
273
|
+
})));
|
|
274
|
+
await renderSettled();
|
|
275
|
+
|
|
276
|
+
expect(testSetup.captureCharFrame()).toContain("Row 530");
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("renders only the visible rows and the rows changed by navigation", async () => {
|
|
280
|
+
let renderedCells = 0;
|
|
247
281
|
testSetup = await testRender(
|
|
248
282
|
<LargeSelectionHarness
|
|
249
283
|
onIsSelected={() => {
|
|
250
|
-
|
|
284
|
+
renderedCells += 1;
|
|
251
285
|
}}
|
|
252
286
|
/>,
|
|
253
287
|
{ width: 60, height: 12 },
|
|
254
288
|
);
|
|
255
289
|
|
|
290
|
+
await renderSettled();
|
|
291
|
+
expect(renderedCells).toBeLessThan(150);
|
|
292
|
+
|
|
293
|
+
const beforeNavigation = renderedCells;
|
|
294
|
+
await emitKeypress({ name: "down", sequence: "\u001B[B" });
|
|
256
295
|
await renderSettled();
|
|
257
296
|
|
|
258
|
-
expect(
|
|
297
|
+
expect(renderedCells - beforeNavigation).toBeLessThanOrEqual(4);
|
|
259
298
|
});
|
|
260
299
|
});
|
|
@@ -80,6 +80,11 @@ export interface DataTableViewProps<
|
|
|
80
80
|
focused?: boolean;
|
|
81
81
|
selection: DataTableSelection<T>;
|
|
82
82
|
onActivate?: (item: T, index: number) => void;
|
|
83
|
+
/**
|
|
84
|
+
* Fires immediately as the lightweight visual cursor moves. Expensive detail
|
|
85
|
+
* work and persisted selection belong in selection.onChange, which is
|
|
86
|
+
* coalesced across keyboard repeats.
|
|
87
|
+
*/
|
|
83
88
|
onCursorChange?: (
|
|
84
89
|
item: T,
|
|
85
90
|
index: number,
|
|
@@ -198,20 +203,54 @@ export function DataTableView<
|
|
|
198
203
|
effectiveSelectedIndexRef.current = effectiveSelectedIndex;
|
|
199
204
|
const [selectionScrollVersion, setSelectionScrollVersion] = useState(0);
|
|
200
205
|
const [selectionScrollTarget, setSelectionScrollTarget] = useState<number | null>(null);
|
|
206
|
+
const selectionScrollTargetRef = useRef<number | null>(null);
|
|
201
207
|
const lastExternalSelectionScrollRef = useRef<{
|
|
202
208
|
kind: DataTableSelection<T>["kind"];
|
|
203
209
|
key: string | number | null;
|
|
204
210
|
resolved: boolean;
|
|
205
211
|
} | null>(null);
|
|
206
|
-
const
|
|
212
|
+
const lastControlledScrollRequestRef = useRef({
|
|
213
|
+
index: scrollToIndex,
|
|
214
|
+
version: scrollToIndexVersion,
|
|
215
|
+
});
|
|
216
|
+
const controlledScrollRequestChanged = scrollToIndex !== undefined
|
|
217
|
+
&& (
|
|
218
|
+
lastControlledScrollRequestRef.current.index !== scrollToIndex
|
|
219
|
+
|| lastControlledScrollRequestRef.current.version !== scrollToIndexVersion
|
|
220
|
+
);
|
|
221
|
+
const effectiveScrollToIndex = controlledScrollRequestChanged
|
|
207
222
|
? scrollToIndex
|
|
208
|
-
: selectionScrollTarget;
|
|
223
|
+
: selectionScrollTarget ?? scrollToIndex;
|
|
209
224
|
|
|
210
225
|
const requestSelectionScroll = useCallback((index: number) => {
|
|
211
|
-
if (
|
|
226
|
+
if (index < 0 || selectionScrollTargetRef.current === index) return;
|
|
227
|
+
const scrollBox = effectiveScrollRef.current;
|
|
228
|
+
const viewportHeight = scrollBox?.viewport?.height;
|
|
229
|
+
const scrollTop = scrollBox?.scrollTop;
|
|
230
|
+
if (
|
|
231
|
+
typeof viewportHeight === "number"
|
|
232
|
+
&& typeof scrollTop === "number"
|
|
233
|
+
&& index >= Math.floor(scrollTop)
|
|
234
|
+
&& index < Math.floor(scrollTop) + Math.max(1, Math.floor(viewportHeight))
|
|
235
|
+
) return;
|
|
236
|
+
selectionScrollTargetRef.current = index;
|
|
212
237
|
setSelectionScrollTarget(index);
|
|
213
238
|
setSelectionScrollVersion((current) => current + 1);
|
|
214
|
-
}, [
|
|
239
|
+
}, [effectiveScrollRef]);
|
|
240
|
+
const clearSelectionScrollTarget = useCallback(() => {
|
|
241
|
+
if (selectionScrollTargetRef.current === null) return;
|
|
242
|
+
selectionScrollTargetRef.current = null;
|
|
243
|
+
setSelectionScrollTarget(null);
|
|
244
|
+
}, []);
|
|
245
|
+
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
lastControlledScrollRequestRef.current = {
|
|
248
|
+
index: scrollToIndex,
|
|
249
|
+
version: scrollToIndexVersion,
|
|
250
|
+
};
|
|
251
|
+
if (scrollToIndex === undefined) return;
|
|
252
|
+
clearSelectionScrollTarget();
|
|
253
|
+
}, [clearSelectionScrollTarget, scrollToIndex, scrollToIndexVersion]);
|
|
215
254
|
|
|
216
255
|
const clearPendingCommit = useCallback(() => {
|
|
217
256
|
if (pendingCommitTimerRef.current) {
|
|
@@ -220,7 +259,8 @@ export function DataTableView<
|
|
|
220
259
|
}
|
|
221
260
|
pendingCommitRef.current = false;
|
|
222
261
|
pendingCommitTargetRef.current = null;
|
|
223
|
-
|
|
262
|
+
clearSelectionScrollTarget();
|
|
263
|
+
}, [clearSelectionScrollTarget]);
|
|
224
264
|
|
|
225
265
|
useEffect(() => {
|
|
226
266
|
if (selection.kind === "none") {
|
|
@@ -268,7 +308,7 @@ export function DataTableView<
|
|
|
268
308
|
lastExternalSelectionScrollRef.current = current;
|
|
269
309
|
|
|
270
310
|
if (selection.kind === "none") {
|
|
271
|
-
|
|
311
|
+
clearSelectionScrollTarget();
|
|
272
312
|
return;
|
|
273
313
|
}
|
|
274
314
|
if (effectiveSelectedIndex < 0) return;
|
|
@@ -281,6 +321,7 @@ export function DataTableView<
|
|
|
281
321
|
requestSelectionScroll(effectiveSelectedIndex);
|
|
282
322
|
}
|
|
283
323
|
}, [
|
|
324
|
+
clearSelectionScrollTarget,
|
|
284
325
|
effectiveSelectedIndex,
|
|
285
326
|
requestSelectionScroll,
|
|
286
327
|
scrollToIndex,
|
|
@@ -360,9 +401,10 @@ export function DataTableView<
|
|
|
360
401
|
pendingCommitRef.current = false;
|
|
361
402
|
const pendingTarget = pendingCommitTargetRef.current;
|
|
362
403
|
pendingCommitTargetRef.current = null;
|
|
404
|
+
clearSelectionScrollTarget();
|
|
363
405
|
commitTarget(pendingTarget, "keyboard");
|
|
364
406
|
}, DATA_TABLE_SELECTION_COMMIT_DELAY_MS);
|
|
365
|
-
}, [commitTarget, getCommitTarget, selection.kind]);
|
|
407
|
+
}, [clearSelectionScrollTarget, commitTarget, getCommitTarget, selection.kind]);
|
|
366
408
|
|
|
367
409
|
const updateCursorIndex = useCallback((
|
|
368
410
|
index: number,
|
|
@@ -375,13 +417,17 @@ export function DataTableView<
|
|
|
375
417
|
if (index < 0 || index >= tableProps.items.length) return;
|
|
376
418
|
const item = tableProps.items[index]!;
|
|
377
419
|
if (isNavigable && !isNavigable(item, index)) return;
|
|
378
|
-
effectiveSelectedIndexRef.current = index;
|
|
379
|
-
setCursorIndex(index);
|
|
380
420
|
const reason = options.reason ?? (options.commit === "deferred"
|
|
381
421
|
? "keyboard"
|
|
382
422
|
: options.commit === "immediate"
|
|
383
423
|
? "pointer"
|
|
384
424
|
: "keyboard");
|
|
425
|
+
if (effectiveSelectedIndexRef.current === index) {
|
|
426
|
+
if (options.commit === "immediate") commitIndexImmediately(index, reason);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
effectiveSelectedIndexRef.current = index;
|
|
430
|
+
setCursorIndex(index);
|
|
385
431
|
onCursorChange?.(item, index, reason);
|
|
386
432
|
if (options.commit === "deferred") {
|
|
387
433
|
requestSelectionScroll(index);
|
package/src/components/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
export { PriceSelectorDialog } from "./price-selector-dialog";
|
|
3
|
-
export { StaticChartSurface } from "./chart/static
|
|
3
|
+
export { StaticChartSurface } from "./chart/static";
|
|
4
4
|
export {
|
|
5
5
|
buildMetricTreemapNavigationTiles,
|
|
6
6
|
findMetricTreemapNeighbor,
|
|
@@ -13,10 +13,15 @@ export type { SpeedometerSegment } from "./speedometer-gauge";
|
|
|
13
13
|
export { TickerListTableView } from "./ticker/list-table-view";
|
|
14
14
|
export type { TickerListVisibleRange } from "./ticker/list-table-view";
|
|
15
15
|
export { TickerBadgeList } from "./ticker/badge/list";
|
|
16
|
+
export { TickerBadgeText } from "./ticker/badge/text";
|
|
16
17
|
export { InputSearchBar } from "./input-search-bar";
|
|
17
18
|
export { isTableScrollNearEnd, useTableLoadMore } from "./table-view-shared";
|
|
18
19
|
export { DataTableView } from "./data-table/view";
|
|
19
|
-
export type {
|
|
20
|
+
export type {
|
|
21
|
+
DataTableKeyEvent,
|
|
22
|
+
DataTableRootKeyContext,
|
|
23
|
+
DataTableSelectionChangeReason,
|
|
24
|
+
} from "./data-table/view";
|
|
20
25
|
export { DataTableStackView } from "./data-table/stack-view";
|
|
21
26
|
export { FeedDataTableStackView } from "./feed-data-table/stack-view";
|
|
22
27
|
export type { FeedDataTableItem } from "./feed-data-table/stack-view";
|
|
@@ -47,6 +52,9 @@ export { EmptyState, PaneStatusBody, loadingText, unavailableText } from "./ui/s
|
|
|
47
52
|
export { getMessageComposerBlockHeight, MessageComposer } from "./ui/message-composer";
|
|
48
53
|
export { NumberField, TextField } from "./ui/fields";
|
|
49
54
|
export { SegmentedControl } from "./ui/toggle";
|
|
55
|
+
export { SelectButton } from "./ui/select-button";
|
|
56
|
+
export type { SelectButtonOption, SelectButtonProps } from "./ui/select-button";
|
|
50
57
|
export { Spinner } from "./ui/loading";
|
|
58
|
+
export { RemoteImage } from "./ui";
|
|
51
59
|
export { Tabs } from "./ui/tabs";
|
|
52
60
|
export { usePaneTicker } from "../state/app/context";
|
|
@@ -21,9 +21,13 @@ afterEach(async () => {
|
|
|
21
21
|
function Harness({
|
|
22
22
|
actions,
|
|
23
23
|
onNavigateDown,
|
|
24
|
+
onBlur,
|
|
25
|
+
onQueryChange,
|
|
24
26
|
}: {
|
|
25
27
|
actions: AppAction[];
|
|
26
28
|
onNavigateDown?: () => void;
|
|
29
|
+
onBlur?: () => void;
|
|
30
|
+
onQueryChange?: (query: string) => void;
|
|
27
31
|
}) {
|
|
28
32
|
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-input-search-bar"));
|
|
29
33
|
const inputRef = useRef<InputRenderable | null>(null);
|
|
@@ -46,8 +50,8 @@ function Harness({
|
|
|
46
50
|
debounceMs={100}
|
|
47
51
|
onNavigateDown={onNavigateDown}
|
|
48
52
|
onFocus={() => {}}
|
|
49
|
-
onBlur={() => {}}
|
|
50
|
-
onQueryChange={() => {}}
|
|
53
|
+
onBlur={onBlur ?? (() => {})}
|
|
54
|
+
onQueryChange={onQueryChange ?? (() => {})}
|
|
51
55
|
/>
|
|
52
56
|
</AppContext>
|
|
53
57
|
);
|
|
@@ -76,6 +80,36 @@ describe("InputSearchBar", () => {
|
|
|
76
80
|
]);
|
|
77
81
|
});
|
|
78
82
|
|
|
83
|
+
test("Escape clears the query and releases the field", async () => {
|
|
84
|
+
// The input consumes every key while focused, so without this there is no
|
|
85
|
+
// way out of a search once it is entered.
|
|
86
|
+
const actions: AppAction[] = [];
|
|
87
|
+
const queries: string[] = [];
|
|
88
|
+
let blurred = false;
|
|
89
|
+
|
|
90
|
+
testSetup = await testRender(
|
|
91
|
+
<Harness
|
|
92
|
+
actions={actions}
|
|
93
|
+
onBlur={() => {
|
|
94
|
+
blurred = true;
|
|
95
|
+
}}
|
|
96
|
+
onQueryChange={(query) => queries.push(query)}
|
|
97
|
+
/>,
|
|
98
|
+
{ width: 40, height: 4 },
|
|
99
|
+
);
|
|
100
|
+
await act(async () => {
|
|
101
|
+
await testSetup!.renderOnce();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await act(async () => {
|
|
105
|
+
testSetup!.renderer.keyInput.emit("keypress", { name: "escape" });
|
|
106
|
+
await testSetup!.renderOnce();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(queries).toEqual([""]);
|
|
110
|
+
expect(blurred).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
79
113
|
test("moves from the active search input to the table with Down", async () => {
|
|
80
114
|
const actions: AppAction[] = [];
|
|
81
115
|
let navigatedDown = 0;
|
|
@@ -14,6 +14,7 @@ export function InputSearchBar({
|
|
|
14
14
|
inputRef,
|
|
15
15
|
placeholder,
|
|
16
16
|
debounceMs,
|
|
17
|
+
glyph = "/",
|
|
17
18
|
normalizeValue = identity,
|
|
18
19
|
onNavigateDown,
|
|
19
20
|
onFocus,
|
|
@@ -28,6 +29,8 @@ export function InputSearchBar({
|
|
|
28
29
|
inputRef: RefObject<InputRenderable | null>;
|
|
29
30
|
placeholder: string;
|
|
30
31
|
debounceMs: number;
|
|
32
|
+
/** Leading marker; override when a pane shows more than one field. */
|
|
33
|
+
glyph?: string;
|
|
31
34
|
normalizeValue?: (value: string) => string;
|
|
32
35
|
onNavigateDown?: () => void;
|
|
33
36
|
onFocus: () => void;
|
|
@@ -47,6 +50,20 @@ export function InputSearchBar({
|
|
|
47
50
|
phase: "before",
|
|
48
51
|
});
|
|
49
52
|
|
|
53
|
+
// A focused input consumes every key, so Escape has to be intercepted before
|
|
54
|
+
// it reaches the field. Without this there is no way back out of a search.
|
|
55
|
+
useShortcut((event) => {
|
|
56
|
+
if (event.name !== "escape") return;
|
|
57
|
+
stopSearchFocusNavigation(event);
|
|
58
|
+
setDraft("");
|
|
59
|
+
onQueryChange("");
|
|
60
|
+
onBlur();
|
|
61
|
+
}, {
|
|
62
|
+
allowEditable: true,
|
|
63
|
+
enabled: focused && active,
|
|
64
|
+
phase: "before",
|
|
65
|
+
});
|
|
66
|
+
|
|
50
67
|
useEffect(() => {
|
|
51
68
|
setDraft(value);
|
|
52
69
|
}, [value]);
|
|
@@ -81,7 +98,7 @@ export function InputSearchBar({
|
|
|
81
98
|
inputRef.current?.focus?.();
|
|
82
99
|
}}
|
|
83
100
|
>
|
|
84
|
-
<Text fg={active ? colors.textBright : colors.textDim}
|
|
101
|
+
<Text fg={active ? colors.textBright : colors.textDim}>{glyph}</Text>
|
|
85
102
|
<Box width={1} />
|
|
86
103
|
<Input
|
|
87
104
|
ref={inputRef}
|
|
@@ -2,34 +2,81 @@ import { afterEach, expect, test } from "bun:test";
|
|
|
2
2
|
import { testRender } from "../../renderers/opentui/test-utils";
|
|
3
3
|
import { AppContext, createInitialState } from "../../state/app/context";
|
|
4
4
|
import { createDefaultConfig } from "../../types/config";
|
|
5
|
-
import { VERSION } from "../../version";
|
|
6
5
|
import { act } from "react";
|
|
7
6
|
import { Header } from "./header";
|
|
7
|
+
import { publishCommandBarPrompt } from "../command-bar/panel/prompt-binding";
|
|
8
8
|
|
|
9
9
|
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
10
10
|
|
|
11
11
|
afterEach(() => {
|
|
12
|
+
publishCommandBarPrompt(null);
|
|
12
13
|
testSetup?.renderer.destroy();
|
|
13
14
|
testSetup = undefined;
|
|
14
15
|
});
|
|
15
16
|
|
|
16
|
-
test("opens the
|
|
17
|
-
let openedVersion = "";
|
|
17
|
+
test("opens the command bar by clicking the header prompt", async () => {
|
|
18
18
|
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-header-test"));
|
|
19
|
+
const actions: Array<{ type: string; open?: boolean; query?: string }> = [];
|
|
19
20
|
testSetup = await testRender(
|
|
20
|
-
<AppContext value={{ state, dispatch: () => {} }}>
|
|
21
|
-
<Header
|
|
21
|
+
<AppContext value={{ state, dispatch: (action) => actions.push(action as { type: string }) }}>
|
|
22
|
+
<Header />
|
|
22
23
|
</AppContext>,
|
|
23
|
-
{ width:
|
|
24
|
+
{ width: 120, height: 1 },
|
|
24
25
|
);
|
|
25
26
|
|
|
26
27
|
await testSetup.renderOnce();
|
|
27
|
-
const
|
|
28
|
-
expect(
|
|
28
|
+
const promptX = testSetup.captureCharFrame().indexOf(">");
|
|
29
|
+
expect(promptX).toBeGreaterThanOrEqual(0);
|
|
30
|
+
|
|
31
|
+
await act(async () => {
|
|
32
|
+
await testSetup!.mockMouse.click(promptX + 2, 0);
|
|
33
|
+
await testSetup!.renderOnce();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(actions).toContainEqual({ type: "SET_COMMAND_BAR", open: true, query: "" });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* While the bar is open the header prompt is its input: what the panel
|
|
41
|
+
* publishes is what the user types into, and a screen without a binding (a
|
|
42
|
+
* workflow with its own fields) must not put a focused input in the header.
|
|
43
|
+
*/
|
|
44
|
+
test("hosts the command bar input while a list screen is published", async () => {
|
|
45
|
+
const state = {
|
|
46
|
+
...createInitialState(createDefaultConfig("/tmp/gloomberb-header-test")),
|
|
47
|
+
commandBarOpen: true,
|
|
48
|
+
};
|
|
49
|
+
const typed: string[] = [];
|
|
50
|
+
testSetup = await testRender(
|
|
51
|
+
<AppContext value={{ state, dispatch: () => {} }}>
|
|
52
|
+
<Header />
|
|
53
|
+
</AppContext>,
|
|
54
|
+
{ width: 120, height: 1 },
|
|
55
|
+
);
|
|
56
|
+
await testSetup.renderOnce();
|
|
57
|
+
expect(testSetup.captureCharFrame()).not.toContain("Search or run a command");
|
|
58
|
+
|
|
59
|
+
await act(async () => {
|
|
60
|
+
publishCommandBarPrompt({
|
|
61
|
+
screenKey: "root:Commands",
|
|
62
|
+
query: "QQ",
|
|
63
|
+
placeholder: "Command or plain English…",
|
|
64
|
+
ghostSuffix: " AAPL",
|
|
65
|
+
onQueryChange: (query) => typed.push(query),
|
|
66
|
+
});
|
|
67
|
+
await testSetup!.renderOnce();
|
|
68
|
+
});
|
|
69
|
+
expect(testSetup.captureCharFrame()).toContain("QQ AAPL");
|
|
70
|
+
|
|
71
|
+
await act(async () => {
|
|
72
|
+
await testSetup!.mockInput.typeText("x");
|
|
73
|
+
await testSetup!.renderOnce();
|
|
74
|
+
});
|
|
75
|
+
expect(typed).toEqual(["QQx"]);
|
|
29
76
|
|
|
30
77
|
await act(async () => {
|
|
31
|
-
|
|
78
|
+
publishCommandBarPrompt(null);
|
|
32
79
|
await testSetup!.renderOnce();
|
|
33
80
|
});
|
|
34
|
-
expect(
|
|
81
|
+
expect(testSetup.captureCharFrame()).not.toContain("QQ");
|
|
35
82
|
});
|