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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { ResolvedSeries } from "../../../time-series/types";
|
|
2
|
+
import type { CompositeTimeScale } from "./types";
|
|
2
3
|
import {
|
|
3
4
|
buildCompositeTimeScale,
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
compositeTimeAtPosition,
|
|
6
|
+
compositeTimePosition,
|
|
6
7
|
} from "./time-scale";
|
|
7
8
|
|
|
8
9
|
export interface CompositeViewportRange {
|
|
@@ -28,31 +29,48 @@ export type CompositeChartInteraction =
|
|
|
28
29
|
|
|
29
30
|
export const COMPOSITE_ZOOM_STEP_FACTOR = 1.2;
|
|
30
31
|
export const COMPOSITE_KEYBOARD_PAN_RATIO = 0.02;
|
|
32
|
+
/** Pixels of trackpad or wheel travel that zoom the viewport by a factor of e. */
|
|
33
|
+
const WHEEL_ZOOM_PIXELS_PER_E = 160;
|
|
34
|
+
/** Bound on one wheel event so a coarse mouse notch cannot zoom by more than this. */
|
|
35
|
+
const WHEEL_ZOOM_STEP_LIMIT = 1.5;
|
|
31
36
|
|
|
32
|
-
const
|
|
37
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
38
|
+
const FALLBACK_SINGLE_POINT_SPAN_MS = DAY_MS;
|
|
39
|
+
/** Bars that stay in view at the deepest zoom. */
|
|
40
|
+
const MINIMUM_VISIBLE_BARS = 2;
|
|
33
41
|
const WHEEL_PAN_RATIO_PER_DELTA = 0.005;
|
|
34
42
|
const MAX_WHEEL_DELTA_MAGNITUDE = 8;
|
|
43
|
+
const WHEEL_ZOOM_RATIO_PER_DELTA = 0.04;
|
|
35
44
|
const SOURCE_VIEWPORT_RESET_RATIO = 0.01;
|
|
36
45
|
const SOURCE_VIEWPORT_RESET_FLOOR_MS = 60_000;
|
|
37
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The space every navigation gesture works in. Market charts collapse closed
|
|
49
|
+
* sessions, so a position is a bar slot there; calendar charts use wall-clock
|
|
50
|
+
* milliseconds. Data refreshes build a new frame; an in-flight gesture keeps
|
|
51
|
+
* the one it started with, so a refresh can never move the view under the
|
|
52
|
+
* pointer.
|
|
53
|
+
*/
|
|
54
|
+
export interface CompositeNavigationFrame {
|
|
55
|
+
scale: CompositeTimeScale;
|
|
56
|
+
dataStart: number;
|
|
57
|
+
dataEnd: number;
|
|
58
|
+
dataStartPosition: number;
|
|
59
|
+
dataEndPosition: number;
|
|
60
|
+
minimumSpanPositions: number;
|
|
61
|
+
/** Wall-clock equivalent of `minimumSpanPositions`, for tools that select time ranges. */
|
|
62
|
+
minimumSpanMs: number;
|
|
63
|
+
/** Fraction of the visible span the viewport may extend before the first observation. */
|
|
64
|
+
historicalPaddingRatio: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
38
67
|
function finiteTime(value: Date | undefined): number | null {
|
|
39
68
|
const time = value?.getTime();
|
|
40
69
|
return typeof time === "number" && Number.isFinite(time) ? time : null;
|
|
41
70
|
}
|
|
42
71
|
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
): CompositeViewportRange | null {
|
|
46
|
-
const start = finiteTime(viewport?.start);
|
|
47
|
-
const end = finiteTime(viewport?.end);
|
|
48
|
-
if (start === null || end === null || start > end) return null;
|
|
49
|
-
if (start < end) {
|
|
50
|
-
return { start: new Date(start), end: new Date(end) };
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
start: new Date(start - FALLBACK_SINGLE_POINT_SPAN_MS / 2),
|
|
54
|
-
end: new Date(end + FALLBACK_SINGLE_POINT_SPAN_MS / 2),
|
|
55
|
-
};
|
|
72
|
+
function clamp(value: number, minimum: number, maximum: number): number {
|
|
73
|
+
return Math.max(minimum, Math.min(maximum, value));
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
function hasRenderableValue(point: ResolvedSeries["points"][number]): boolean {
|
|
@@ -62,170 +80,140 @@ function hasRenderableValue(point: ResolvedSeries["points"][number]): boolean {
|
|
|
62
80
|
);
|
|
63
81
|
}
|
|
64
82
|
|
|
65
|
-
function
|
|
66
|
-
|
|
83
|
+
function dataExtent(series: readonly ResolvedSeries[]): { start: number; end: number } | null {
|
|
84
|
+
let start = Number.POSITIVE_INFINITY;
|
|
85
|
+
let end = Number.NEGATIVE_INFINITY;
|
|
67
86
|
for (const entry of series) {
|
|
68
87
|
for (const point of entry.points) {
|
|
69
88
|
if (!hasRenderableValue(point)) continue;
|
|
70
|
-
const timestamp = point.date
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (
|
|
89
|
+
const timestamp = point.date.getTime();
|
|
90
|
+
if (!Number.isFinite(timestamp)) continue;
|
|
91
|
+
if (timestamp < start) start = timestamp;
|
|
92
|
+
if (timestamp > end) end = timestamp;
|
|
74
93
|
}
|
|
75
94
|
}
|
|
76
|
-
return
|
|
95
|
+
return start <= end ? { start, end } : null;
|
|
77
96
|
}
|
|
78
97
|
|
|
79
|
-
function
|
|
80
|
-
timestamps
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const required = Math.max(1, Math.floor(minimumCount));
|
|
88
|
-
let count = 0;
|
|
89
|
-
for (const timestamp of timestamps) {
|
|
90
|
-
if (timestamp < start) continue;
|
|
91
|
-
if (timestamp > end) break;
|
|
92
|
-
count += 1;
|
|
93
|
-
if (count >= required) return true;
|
|
98
|
+
function medianStepMs(series: readonly ResolvedSeries[]): number | null {
|
|
99
|
+
const timestamps = new Set<number>();
|
|
100
|
+
for (const entry of series) {
|
|
101
|
+
for (const point of entry.points) {
|
|
102
|
+
if (!hasRenderableValue(point)) continue;
|
|
103
|
+
const timestamp = point.date.getTime();
|
|
104
|
+
if (Number.isFinite(timestamp)) timestamps.add(timestamp);
|
|
105
|
+
}
|
|
94
106
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
103
|
-
|
|
107
|
+
const sorted = [...timestamps].sort((left, right) => left - right);
|
|
108
|
+
const steps: number[] = [];
|
|
109
|
+
for (let index = 1; index < sorted.length; index += 1) {
|
|
110
|
+
const step = sorted[index]! - sorted[index - 1]!;
|
|
111
|
+
if (step > 0) steps.push(step);
|
|
112
|
+
}
|
|
113
|
+
if (steps.length === 0) return null;
|
|
114
|
+
steps.sort((left, right) => left - right);
|
|
115
|
+
// An upper median keeps one near-duplicate live observation from lowering
|
|
116
|
+
// a daily chart's zoom floor.
|
|
117
|
+
return steps[Math.floor(steps.length / 2)]!;
|
|
104
118
|
}
|
|
105
119
|
|
|
106
|
-
export function
|
|
107
|
-
series: ResolvedSeries[],
|
|
108
|
-
|
|
120
|
+
export function buildCompositeNavigationFrame(
|
|
121
|
+
series: readonly ResolvedSeries[],
|
|
122
|
+
timelineSeries: readonly ResolvedSeries[],
|
|
109
123
|
options: { historicalPaddingRatio?: number } = {},
|
|
110
|
-
):
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const overlapsData = requested.end.getTime() >= data.start.getTime()
|
|
121
|
-
&& requested.start.getTime() <= data.end.getTime();
|
|
122
|
-
// Loaded series can include a buffer outside the authored viewport. Use the
|
|
123
|
-
// complete real-data extent when the two ranges overlap. A disjoint request
|
|
124
|
-
// keeps its own window, unioned with the data so navigation can always get
|
|
125
|
-
// back: bounds equal to an observation-free window strand pan and zoom.
|
|
126
|
-
if (!overlapsData) {
|
|
127
|
-
return {
|
|
128
|
-
start: new Date(Math.min(data.start.getTime(), requested.start.getTime())),
|
|
129
|
-
end: new Date(Math.max(data.end.getTime(), requested.end.getTime())),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
const paddingRatio = Math.max(options.historicalPaddingRatio ?? 0, 0);
|
|
133
|
-
if (paddingRatio === 0) return data;
|
|
134
|
-
const requestedSpan = Math.max(requested.end.getTime() - requested.start.getTime(), 1);
|
|
124
|
+
): CompositeNavigationFrame | null {
|
|
125
|
+
const extent = dataExtent(series);
|
|
126
|
+
if (!extent) return null;
|
|
127
|
+
const scale = buildCompositeTimeScale(timelineSeries, extent.start, extent.end);
|
|
128
|
+
const dataStartPosition = compositeTimePosition(scale, extent.start);
|
|
129
|
+
const dataEndPosition = compositeTimePosition(scale, extent.end);
|
|
130
|
+
const minimumSpanMs = scale.kind === "market"
|
|
131
|
+
? scale.cadenceMs * MINIMUM_VISIBLE_BARS
|
|
132
|
+
: (medianStepMs(series) ?? FALLBACK_SINGLE_POINT_SPAN_MS) * MINIMUM_VISIBLE_BARS;
|
|
133
|
+
const minimumSpanPositions = scale.kind === "market" ? MINIMUM_VISIBLE_BARS : minimumSpanMs;
|
|
135
134
|
return {
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
scale,
|
|
136
|
+
dataStart: extent.start,
|
|
137
|
+
dataEnd: extent.end,
|
|
138
|
+
dataStartPosition,
|
|
139
|
+
dataEndPosition,
|
|
140
|
+
minimumSpanPositions: Math.max(minimumSpanPositions, Number.EPSILON),
|
|
141
|
+
minimumSpanMs: Math.max(minimumSpanMs, 1),
|
|
142
|
+
historicalPaddingRatio: Math.max(options.historicalPaddingRatio ?? 0, 0),
|
|
138
143
|
};
|
|
139
144
|
}
|
|
140
145
|
|
|
141
|
-
export function
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
): number {
|
|
145
|
-
const start = bounds.start.getTime();
|
|
146
|
-
const end = bounds.end.getTime();
|
|
147
|
-
const timestamps = pointTimestamps(series).filter((timestamp) => timestamp >= start && timestamp <= end);
|
|
148
|
-
const steps: number[] = [];
|
|
149
|
-
for (let index = 1; index < timestamps.length; index += 1) {
|
|
150
|
-
const step = timestamps[index]! - timestamps[index - 1]!;
|
|
151
|
-
if (step > 0) steps.push(step);
|
|
146
|
+
export function compositeNavigationDataViewport(frame: CompositeNavigationFrame): CompositeViewportRange {
|
|
147
|
+
if (frame.dataStart < frame.dataEnd) {
|
|
148
|
+
return { start: new Date(frame.dataStart), end: new Date(frame.dataEnd) };
|
|
152
149
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const representativeStep = steps[Math.floor(steps.length / 2)];
|
|
158
|
-
return representativeStep !== undefined
|
|
159
|
-
? Math.min(Math.max(representativeStep, 1), boundsSpan)
|
|
160
|
-
: Math.max(Math.min(boundsSpan, FALLBACK_SINGLE_POINT_SPAN_MS), 1);
|
|
150
|
+
return {
|
|
151
|
+
start: new Date(frame.dataStart - FALLBACK_SINGLE_POINT_SPAN_MS / 2),
|
|
152
|
+
end: new Date(frame.dataEnd + FALLBACK_SINGLE_POINT_SPAN_MS / 2),
|
|
153
|
+
};
|
|
161
154
|
}
|
|
162
155
|
|
|
163
|
-
|
|
164
|
-
|
|
156
|
+
interface PositionRange {
|
|
157
|
+
start: number;
|
|
158
|
+
end: number;
|
|
165
159
|
}
|
|
166
160
|
|
|
167
|
-
function
|
|
161
|
+
export function compositeViewportPositions(
|
|
162
|
+
frame: CompositeNavigationFrame,
|
|
168
163
|
viewport: CompositeViewportRange,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (!series?.some((entry) => entry.timeBasis?.kind === "market")) return null;
|
|
177
|
-
const scale = buildCompositeTimeScale(
|
|
178
|
-
series,
|
|
179
|
-
bounds.start.getTime(),
|
|
180
|
-
bounds.end.getTime(),
|
|
181
|
-
);
|
|
182
|
-
if (scale.kind !== "market") return null;
|
|
183
|
-
const startRatio = projectCompositeTimestamp(scale, viewport.start.getTime())?.ratio;
|
|
184
|
-
const endRatio = projectCompositeTimestamp(scale, viewport.end.getTime())?.ratio;
|
|
185
|
-
if (
|
|
186
|
-
typeof startRatio !== "number"
|
|
187
|
-
|| !Number.isFinite(startRatio)
|
|
188
|
-
|| typeof endRatio !== "number"
|
|
189
|
-
|| !Number.isFinite(endRatio)
|
|
190
|
-
|| startRatio >= endRatio
|
|
191
|
-
) {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
return { scale, startRatio, endRatio };
|
|
164
|
+
): PositionRange | null {
|
|
165
|
+
const startTime = finiteTime(viewport.start);
|
|
166
|
+
const endTime = finiteTime(viewport.end);
|
|
167
|
+
if (startTime === null || endTime === null || startTime > endTime) return null;
|
|
168
|
+
const start = compositeTimePosition(frame.scale, startTime);
|
|
169
|
+
const end = compositeTimePosition(frame.scale, endTime);
|
|
170
|
+
return { start, end: Math.max(end, start) };
|
|
195
171
|
}
|
|
196
172
|
|
|
197
|
-
function
|
|
198
|
-
scale: Extract<ReturnType<typeof buildCompositeTimeScale>, { kind: "market" }>,
|
|
199
|
-
startRatio: number,
|
|
200
|
-
endRatio: number,
|
|
201
|
-
): CompositeViewportRange {
|
|
173
|
+
function viewportFromPositions(frame: CompositeNavigationFrame, range: PositionRange): CompositeViewportRange {
|
|
202
174
|
return {
|
|
203
|
-
start: new Date(
|
|
204
|
-
end: new Date(
|
|
175
|
+
start: new Date(Math.round(compositeTimeAtPosition(frame.scale, range.start))),
|
|
176
|
+
end: new Date(Math.round(compositeTimeAtPosition(frame.scale, range.end))),
|
|
205
177
|
};
|
|
206
178
|
}
|
|
207
179
|
|
|
180
|
+
function clampPositions(
|
|
181
|
+
frame: CompositeNavigationFrame,
|
|
182
|
+
range: PositionRange,
|
|
183
|
+
paddingRatio: number,
|
|
184
|
+
): PositionRange {
|
|
185
|
+
const dataSpan = Math.max(frame.dataEndPosition - frame.dataStartPosition, 0);
|
|
186
|
+
const maximumSpan = Math.max(dataSpan * (1 + paddingRatio), frame.minimumSpanPositions);
|
|
187
|
+
const span = clamp(range.end - range.start, frame.minimumSpanPositions, maximumSpan);
|
|
188
|
+
let start = Math.max(range.start, frame.dataStartPosition - span * paddingRatio);
|
|
189
|
+
// The newest observation is the hard edge: nothing lies beyond it yet.
|
|
190
|
+
start = Math.min(start, frame.dataEndPosition - span);
|
|
191
|
+
return { start, end: start + span };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Fits an authored viewport to the loaded data, keeping its wall-clock span:
|
|
196
|
+
* a one-day window that ends after the close still shows one session. One
|
|
197
|
+
* that misses the data entirely is left alone so the time axis still says
|
|
198
|
+
* where the chart is and navigation can bring it back.
|
|
199
|
+
*/
|
|
208
200
|
export function clampCompositeViewport(
|
|
201
|
+
frame: CompositeNavigationFrame,
|
|
209
202
|
viewport: CompositeViewportRange,
|
|
210
|
-
bounds: CompositeViewportRange,
|
|
211
203
|
): CompositeViewportRange {
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
start: new Date(start),
|
|
227
|
-
end: new Date(start + span),
|
|
228
|
-
};
|
|
204
|
+
const data = compositeNavigationDataViewport(frame);
|
|
205
|
+
const startTime = finiteTime(viewport.start);
|
|
206
|
+
const endTime = finiteTime(viewport.end);
|
|
207
|
+
if (startTime === null || endTime === null || startTime > endTime) return data;
|
|
208
|
+
const overlapsData = endTime >= frame.dataStart && startTime <= frame.dataEnd;
|
|
209
|
+
if (!overlapsData) return viewport;
|
|
210
|
+
const dataStart = data.start.getTime();
|
|
211
|
+
const dataEnd = data.end.getTime();
|
|
212
|
+
const span = Math.min(endTime - startTime, dataEnd - dataStart);
|
|
213
|
+
const start = clamp(startTime, dataStart, dataEnd - span);
|
|
214
|
+
return start === startTime && start + span === endTime
|
|
215
|
+
? viewport
|
|
216
|
+
: { start: new Date(start), end: new Date(start + span) };
|
|
229
217
|
}
|
|
230
218
|
|
|
231
219
|
export function sameCompositeViewport(
|
|
@@ -237,138 +225,100 @@ export function sameCompositeViewport(
|
|
|
237
225
|
&& left.end.getTime() === right.end.getTime();
|
|
238
226
|
}
|
|
239
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Positive deltas move toward older observations, matching the legacy chart:
|
|
230
|
+
* dragging right or scrolling up/left reveals earlier dates.
|
|
231
|
+
*/
|
|
232
|
+
export function panCompositeViewport(
|
|
233
|
+
frame: CompositeNavigationFrame,
|
|
234
|
+
viewport: CompositeViewportRange,
|
|
235
|
+
deltaPositions: number,
|
|
236
|
+
): CompositeViewportRange {
|
|
237
|
+
const positions = compositeViewportPositions(frame, viewport);
|
|
238
|
+
if (!positions) return compositeNavigationDataViewport(frame);
|
|
239
|
+
const shift = Number.isFinite(deltaPositions) ? deltaPositions : 0;
|
|
240
|
+
const clamped = clampPositions(
|
|
241
|
+
frame,
|
|
242
|
+
{ start: positions.start - shift, end: positions.end - shift },
|
|
243
|
+
frame.historicalPaddingRatio,
|
|
244
|
+
);
|
|
245
|
+
return viewportFromPositions(frame, clamped);
|
|
246
|
+
}
|
|
247
|
+
|
|
240
248
|
export function zoomCompositeViewport(
|
|
249
|
+
frame: CompositeNavigationFrame,
|
|
241
250
|
viewport: CompositeViewportRange,
|
|
242
|
-
bounds: CompositeViewportRange,
|
|
243
251
|
zoomFactor: number,
|
|
244
252
|
anchorRatio: number,
|
|
245
|
-
minimumSpanMs: number,
|
|
246
|
-
series?: ResolvedSeries[],
|
|
247
253
|
): CompositeViewportRange {
|
|
248
|
-
const
|
|
249
|
-
if (!
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
const end = current.end.getTime();
|
|
253
|
-
const boundsSpan = Math.max(bounds.end.getTime() - bounds.start.getTime(), 1);
|
|
254
|
-
const currentSpan = Math.max(end - start, 1);
|
|
254
|
+
const positions = compositeViewportPositions(frame, viewport);
|
|
255
|
+
if (!positions) return compositeNavigationDataViewport(frame);
|
|
256
|
+
if (!Number.isFinite(zoomFactor) || zoomFactor <= 0) return viewport;
|
|
257
|
+
const span = Math.max(positions.end - positions.start, frame.minimumSpanPositions);
|
|
255
258
|
const ratio = clamp(anchorRatio, 0, 1);
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const currentMarketSpan = endRatio - startRatio;
|
|
265
|
-
const totalMarketPositions = Math.max(
|
|
266
|
-
scale.endPosition - scale.startPosition,
|
|
267
|
-
Number.EPSILON,
|
|
268
|
-
);
|
|
269
|
-
const minimumMarketSpan = clamp(
|
|
270
|
-
Math.max(minimumSpanMs, 1) / scale.cadenceMs / totalMarketPositions,
|
|
271
|
-
Number.EPSILON,
|
|
272
|
-
1,
|
|
273
|
-
);
|
|
274
|
-
const nextMarketSpan = clamp(
|
|
275
|
-
currentMarketSpan / zoomFactor,
|
|
276
|
-
minimumMarketSpan,
|
|
277
|
-
1,
|
|
278
|
-
);
|
|
279
|
-
const anchor = startRatio + currentMarketSpan * ratio;
|
|
280
|
-
const candidateStart = clamp(
|
|
281
|
-
anchor - nextMarketSpan * ratio,
|
|
282
|
-
0,
|
|
283
|
-
1 - nextMarketSpan,
|
|
284
|
-
);
|
|
285
|
-
return viewportFromMarketRatios(
|
|
286
|
-
scale,
|
|
287
|
-
candidateStart,
|
|
288
|
-
candidateStart + nextMarketSpan,
|
|
289
|
-
);
|
|
290
|
-
})()
|
|
291
|
-
: (() => {
|
|
292
|
-
const nextSpan = clamp(
|
|
293
|
-
currentSpan / zoomFactor,
|
|
294
|
-
Math.min(Math.max(minimumSpanMs, 1), boundsSpan),
|
|
295
|
-
boundsSpan,
|
|
296
|
-
);
|
|
297
|
-
const anchor = start + currentSpan * ratio;
|
|
298
|
-
return clampCompositeViewport({
|
|
299
|
-
start: new Date(anchor - nextSpan * ratio),
|
|
300
|
-
end: new Date(anchor + nextSpan * (1 - ratio)),
|
|
301
|
-
}, bounds);
|
|
302
|
-
})();
|
|
303
|
-
if (!series) return candidate;
|
|
304
|
-
|
|
305
|
-
const timestamps = pointTimestamps(series)
|
|
306
|
-
.filter((timestamp) => timestamp >= bounds.start.getTime() && timestamp <= bounds.end.getTime());
|
|
307
|
-
if (timestamps.length < 2 || viewportHasTimestamps(timestamps, candidate, 2)) {
|
|
308
|
-
return candidate;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const currentHasObservations = viewportHasTimestamps(timestamps, current, 2);
|
|
312
|
-
const candidateSpan = candidate.end.getTime() - candidate.start.getTime();
|
|
313
|
-
// An adaptive response can invalidate an existing viewport. Let zoom-out
|
|
314
|
-
// keep expanding from that state until observations re-enter the window.
|
|
315
|
-
if (!currentHasObservations && candidateSpan > currentSpan) return candidate;
|
|
316
|
-
return current;
|
|
259
|
+
const anchor = positions.start + span * ratio;
|
|
260
|
+
const nextSpan = span / zoomFactor;
|
|
261
|
+
const clamped = clampPositions(
|
|
262
|
+
frame,
|
|
263
|
+
{ start: anchor - nextSpan * ratio, end: anchor + nextSpan * (1 - ratio) },
|
|
264
|
+
frame.historicalPaddingRatio,
|
|
265
|
+
);
|
|
266
|
+
return viewportFromPositions(frame, clamped);
|
|
317
267
|
}
|
|
318
268
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
* dragging right or scrolling up/left reveals earlier dates.
|
|
322
|
-
*/
|
|
323
|
-
export function panCompositeViewport(
|
|
269
|
+
export function fitCompositeViewport(
|
|
270
|
+
frame: CompositeNavigationFrame,
|
|
324
271
|
viewport: CompositeViewportRange,
|
|
325
|
-
bounds: CompositeViewportRange,
|
|
326
|
-
shiftRatio: number,
|
|
327
|
-
series?: ResolvedSeries[],
|
|
328
|
-
allowEmpty = false,
|
|
329
272
|
): CompositeViewportRange {
|
|
330
|
-
const
|
|
331
|
-
if (!
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
// changes how many slots stay visible and the plot accordions instead of
|
|
335
|
-
// translating. Shift in slot space so the visible span is preserved.
|
|
336
|
-
const candidate = marketProjection
|
|
337
|
-
? (() => {
|
|
338
|
-
const { scale, startRatio, endRatio } = marketProjection;
|
|
339
|
-
const marketSpan = endRatio - startRatio;
|
|
340
|
-
const nextStart = clamp(startRatio - marketSpan * shiftRatio, 0, 1 - marketSpan);
|
|
341
|
-
return clampCompositeViewport(
|
|
342
|
-
viewportFromMarketRatios(scale, nextStart, nextStart + marketSpan),
|
|
343
|
-
bounds,
|
|
344
|
-
);
|
|
345
|
-
})()
|
|
346
|
-
: (() => {
|
|
347
|
-
const shift = Math.max(current.end.getTime() - current.start.getTime(), 1) * shiftRatio;
|
|
348
|
-
return clampCompositeViewport({
|
|
349
|
-
start: new Date(current.start.getTime() - shift),
|
|
350
|
-
end: new Date(current.end.getTime() - shift),
|
|
351
|
-
}, bounds);
|
|
352
|
-
})();
|
|
353
|
-
if (!series || allowEmpty) return candidate;
|
|
273
|
+
const positions = compositeViewportPositions(frame, viewport);
|
|
274
|
+
if (!positions) return compositeNavigationDataViewport(frame);
|
|
275
|
+
return viewportFromPositions(frame, clampPositions(frame, positions, frame.historicalPaddingRatio));
|
|
276
|
+
}
|
|
354
277
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
278
|
+
export interface CompositeWheelGesture {
|
|
279
|
+
direction: "up" | "down" | "left" | "right";
|
|
280
|
+
delta?: number;
|
|
281
|
+
/** Signed pixel deltas when the host reports them; terminals report notches only. */
|
|
282
|
+
deltaX?: number;
|
|
283
|
+
deltaY?: number;
|
|
360
284
|
}
|
|
361
285
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Pan distance for one wheel event as a fraction of the visible span. Pixel
|
|
288
|
+
* hosts move the plot exactly as far as the fingers travelled, so the sum of
|
|
289
|
+
* both axes pans and a diagonal swipe never flickers between them. Notch hosts
|
|
290
|
+
* step a bounded fraction per event.
|
|
291
|
+
*/
|
|
292
|
+
export function resolveCompositeWheelPan(
|
|
293
|
+
gesture: CompositeWheelGesture,
|
|
294
|
+
plotPixelWidth: number,
|
|
365
295
|
): number {
|
|
366
|
-
|
|
296
|
+
if (typeof gesture.deltaX === "number" && typeof gesture.deltaY === "number") {
|
|
297
|
+
const travel = gesture.deltaX + gesture.deltaY;
|
|
298
|
+
if (!Number.isFinite(travel) || plotPixelWidth <= 0) return 0;
|
|
299
|
+
return -travel / plotPixelWidth;
|
|
300
|
+
}
|
|
301
|
+
const rawMagnitude = Math.abs(gesture.delta ?? 1);
|
|
367
302
|
const magnitude = clamp(rawMagnitude > 0 ? rawMagnitude : 1, 1, MAX_WHEEL_DELTA_MAGNITUDE);
|
|
368
|
-
const directionSign = direction === "up" || direction === "left" ? 1 : -1;
|
|
303
|
+
const directionSign = gesture.direction === "up" || gesture.direction === "left" ? 1 : -1;
|
|
369
304
|
return directionSign * magnitude * WHEEL_PAN_RATIO_PER_DELTA;
|
|
370
305
|
}
|
|
371
306
|
|
|
307
|
+
/** Zoom factor for one modifier-wheel or pinch event; greater than one zooms in. */
|
|
308
|
+
export function resolveCompositeWheelZoom(gesture: CompositeWheelGesture): number {
|
|
309
|
+
if (typeof gesture.deltaY === "number") {
|
|
310
|
+
if (!Number.isFinite(gesture.deltaY) || gesture.deltaY === 0) return 1;
|
|
311
|
+
return clamp(
|
|
312
|
+
Math.exp(-gesture.deltaY / WHEEL_ZOOM_PIXELS_PER_E),
|
|
313
|
+
1 / WHEEL_ZOOM_STEP_LIMIT,
|
|
314
|
+
WHEEL_ZOOM_STEP_LIMIT,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
const magnitude = clamp(Math.abs(gesture.delta ?? 1), 1, MAX_WHEEL_DELTA_MAGNITUDE);
|
|
318
|
+
const step = 1 + magnitude * WHEEL_ZOOM_RATIO_PER_DELTA;
|
|
319
|
+
return gesture.direction === "up" ? step : 1 / step;
|
|
320
|
+
}
|
|
321
|
+
|
|
372
322
|
export function shouldResetCompositeViewport(
|
|
373
323
|
previous: CompositeViewportRange | null,
|
|
374
324
|
next: CompositeViewportRange | null,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { buildCursorPriceAxisOverlay } from "./price-axis-labels";
|
|
3
|
+
|
|
4
|
+
describe("buildCursorPriceAxisOverlay", () => {
|
|
5
|
+
test("positions desktop cursor y-axis overlay from exact pixels", () => {
|
|
6
|
+
const height = 8;
|
|
7
|
+
const cellHeightPx = 18;
|
|
8
|
+
const cursorPixelY = 45.25;
|
|
9
|
+
const overlay = buildCursorPriceAxisOverlay({
|
|
10
|
+
axisWidth: 7,
|
|
11
|
+
axisSectionWidth: 8,
|
|
12
|
+
height,
|
|
13
|
+
cursorPixelY,
|
|
14
|
+
cursorLabel: "$214.03",
|
|
15
|
+
cellHeightPx,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(overlay.labelText).toBe("$214.03");
|
|
19
|
+
expect(overlay.topPercent).toBeCloseTo((cursorPixelY / (height * cellHeightPx - 1)) * 100, 5);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import { Box, Text, useUiCapabilities } from "
|
|
3
|
-
import { colors } from "
|
|
4
|
-
import { formatAxisCell } from "./core/renderer";
|
|
2
|
+
import { Box, Text, useUiCapabilities } from "../../../ui";
|
|
3
|
+
import { colors } from "../../../theme/colors";
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
function formatAxisCell(label: string | null, width: number): string {
|
|
6
|
+
if (width <= 0) return "";
|
|
7
|
+
if (!label) return " ".repeat(width);
|
|
8
|
+
return label.length >= width ? label.slice(0, width) : label.padStart(width);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface PriceAxisMarker {
|
|
7
12
|
row: number;
|
|
8
13
|
pixelY: number;
|
|
9
14
|
label: string;
|