@gloom-sh/terminal 0.18.5 → 0.19.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/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +140 -7
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +17 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +11 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/types.ts +70 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/market.ts +72 -26
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +61 -12
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +55 -34
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +8 -0
- package/src/cli/glossary.ts +25 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +180 -52
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +86 -3
- package/src/cli/pane-functions/function-help.ts +13 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +49 -14
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +22 -11
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +71 -137
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +336 -77
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +24 -6
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +33 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +27 -3
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +2 -2
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +44 -3
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/financials.ts +32 -1
- package/src/types/headless.ts +21 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DataTableColumn } from "../../../components";
|
|
2
|
+
import { formatNumber } from "../../../utils/format";
|
|
2
3
|
|
|
3
4
|
export const TREASURY_DAILY_PANE_ID = "treasury-daily";
|
|
4
5
|
|
|
@@ -23,6 +24,51 @@ export interface OperatingCash {
|
|
|
23
24
|
rows: CashAccount[];
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
/** One day's Treasury General Account closing balance, millions of dollars. */
|
|
28
|
+
export interface TgaClose {
|
|
29
|
+
date: string;
|
|
30
|
+
millions: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The longest stretch of closing balances the Cash tab charts, and the ranges it offers. */
|
|
34
|
+
export type TgaRange = "3M" | "6M" | "1Y" | "ALL";
|
|
35
|
+
export const TGA_RANGES: ReadonlyArray<{ value: TgaRange; label: string; hint: string; months: number | null }> = [
|
|
36
|
+
{ value: "3M", label: "3M", hint: "1", months: 3 },
|
|
37
|
+
{ value: "6M", label: "6M", hint: "2", months: 6 },
|
|
38
|
+
{ value: "1Y", label: "1Y", hint: "3", months: 12 },
|
|
39
|
+
{ value: "ALL", label: "All", hint: "4", months: null },
|
|
40
|
+
];
|
|
41
|
+
const DEFAULT_TGA_RANGE: TgaRange = "1Y";
|
|
42
|
+
|
|
43
|
+
export function tgaRange(value: unknown): TgaRange {
|
|
44
|
+
return TGA_RANGES.find((range) => range.value === value)?.value ?? DEFAULT_TGA_RANGE;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The closes of the last `range`, counted back from the newest one, oldest first. */
|
|
48
|
+
export function tgaClosesInRange(closes: readonly TgaClose[], range: TgaRange): TgaClose[] {
|
|
49
|
+
const months = TGA_RANGES.find((entry) => entry.value === range)?.months ?? null;
|
|
50
|
+
const last = closes.at(-1);
|
|
51
|
+
if (months == null || !last) return [...closes];
|
|
52
|
+
const start = new Date(`${last.date}T00:00:00Z`);
|
|
53
|
+
start.setUTCMonth(start.getUTCMonth() - months);
|
|
54
|
+
const from = start.toISOString().slice(0, 10);
|
|
55
|
+
return closes.filter((close) => close.date >= from);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* What the Treasury's statement calls a line of the Treasury General Account,
|
|
60
|
+
* said plainly: `Treasury General Account (TGA) Opening Balance` is the
|
|
61
|
+
* opening balance, `Total TGA Withdrawals (Table II) (-)` the withdrawals. A
|
|
62
|
+
* line the statement adds later keeps its own words, less the table reference.
|
|
63
|
+
*/
|
|
64
|
+
export function cashLineLabel(account: string): string {
|
|
65
|
+
if (/^Treasury General Account \(TGA\) Opening Balance$/i.test(account)) return "Opening balance";
|
|
66
|
+
if (/^Treasury General Account \(TGA\) Closing Balance$/i.test(account)) return "Closing balance";
|
|
67
|
+
if (/^Total TGA Deposits\b/i.test(account)) return "Deposits";
|
|
68
|
+
if (/^Total TGA Withdrawals\b/i.test(account)) return "Withdrawals";
|
|
69
|
+
return account.replace(/\s*\(Table [IVX]+\)/g, "").replace(/\s*\(-\)$/, "").trim();
|
|
70
|
+
}
|
|
71
|
+
|
|
26
72
|
export interface DebtDay {
|
|
27
73
|
id: string;
|
|
28
74
|
date: string;
|
|
@@ -40,7 +86,7 @@ export type CashColumn = DataTableColumn & { id: CashColumnId };
|
|
|
40
86
|
export type DebtColumn = DataTableColumn & { id: DebtColumnId };
|
|
41
87
|
|
|
42
88
|
export const CASH_COLUMNS: CashColumn[] = [
|
|
43
|
-
{ id: "account", label: "Account", width:
|
|
89
|
+
{ id: "account", label: "Treasury General Account", width: 26, align: "left", flexGrow: 1 },
|
|
44
90
|
{ id: "today", label: "Today $bn", width: 12, align: "right" },
|
|
45
91
|
{ id: "month", label: "This month $bn", width: 15, align: "right" },
|
|
46
92
|
];
|
|
@@ -52,10 +98,11 @@ export const DEBT_COLUMNS: DebtColumn[] = [
|
|
|
52
98
|
{ id: "total", label: "Total $tn", width: 12, align: "right" },
|
|
53
99
|
];
|
|
54
100
|
|
|
101
|
+
/** Two decimals with thousands separators, as every money table writes a large figure: `2,594.63`. */
|
|
55
102
|
function scaled(value: unknown, divisor: number): string {
|
|
56
103
|
if (typeof value !== "number" || !Number.isFinite(value)) return "--";
|
|
57
|
-
const
|
|
58
|
-
return
|
|
104
|
+
const rounded = Number((value / divisor).toFixed(2));
|
|
105
|
+
return formatNumber(rounded === 0 ? 0 : rounded, 2);
|
|
59
106
|
}
|
|
60
107
|
|
|
61
108
|
/** Millions of dollars, drawn as billions. */
|
|
@@ -1,33 +1,52 @@
|
|
|
1
1
|
import { useCallback, useMemo, useState } from "react";
|
|
2
2
|
import {
|
|
3
|
+
ChartTableHeader,
|
|
3
4
|
DataTableView,
|
|
4
5
|
PaneStatusBody,
|
|
6
|
+
QueryBar,
|
|
7
|
+
usePaneNoticeFooter,
|
|
5
8
|
usePaneStatusFooter,
|
|
6
9
|
usePaneTabs,
|
|
10
|
+
type ChartTableChart,
|
|
7
11
|
type DataTableCell,
|
|
12
|
+
type DataTableKeyEvent,
|
|
8
13
|
} from "../../../components";
|
|
9
|
-
import {
|
|
14
|
+
import { spanAxisFormatter } from "../../../components/chart-table";
|
|
15
|
+
import { useChartViewReset } from "../../../components/chart/core/view-reset";
|
|
16
|
+
import { scalarPoint, staticSeries } from "../../../components/chart/static/series";
|
|
17
|
+
import { handleRefreshKey, usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
10
18
|
import { useAsyncResource, useAutoRefresh, usePluginPaneState } from "../../../public/react";
|
|
11
19
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
12
20
|
import type { PaneProps } from "../../../types/plugin";
|
|
13
21
|
import { Box } from "../../../ui";
|
|
22
|
+
import { formatNumber } from "../../../utils/format";
|
|
23
|
+
import { isPlainKeyboardEvent } from "../../../utils/keyboard";
|
|
14
24
|
import { fetchTreasuryDaily } from "./client";
|
|
15
25
|
import {
|
|
16
26
|
CASH_COLUMNS,
|
|
17
27
|
DEBT_COLUMNS,
|
|
28
|
+
TGA_RANGES,
|
|
18
29
|
TREASURY_DAILY_PANE_ID,
|
|
19
30
|
TREASURY_TABS,
|
|
20
31
|
formatBillions,
|
|
21
32
|
formatTrillions,
|
|
33
|
+
tgaClosesInRange,
|
|
34
|
+
tgaRange,
|
|
22
35
|
type CashAccount,
|
|
23
36
|
type CashColumn,
|
|
24
37
|
type DebtColumn,
|
|
25
38
|
type DebtDay,
|
|
39
|
+
type TgaRange,
|
|
26
40
|
type TreasuryTab,
|
|
27
41
|
} from "./model";
|
|
28
42
|
|
|
29
43
|
const EMPTY_CASH: CashAccount[] = [];
|
|
30
44
|
const EMPTY_DEBT: DebtDay[] = [];
|
|
45
|
+
const RANGE_OPTIONS = TGA_RANGES.map(({ value, label, hint }) => ({ value, label, hint }));
|
|
46
|
+
const TGA_SERIES_LABEL = "TGA closing balance $bn";
|
|
47
|
+
/** Ticks in billions, with thousands separators and the decimals the plotted range needs. */
|
|
48
|
+
const formatBillionsAxis = spanAxisFormatter((value, digits) => formatNumber(value, digits));
|
|
49
|
+
const formatBillionsLevel = (value: number) => formatNumber(value, 2);
|
|
31
50
|
|
|
32
51
|
function scaledCell(value: number | null, text: string, muted: string): DataTableCell {
|
|
33
52
|
return value == null ? { text, value, color: muted } : { text, value };
|
|
@@ -44,6 +63,9 @@ export function TreasuryDailyPane({ width, height, focused }: PaneProps) {
|
|
|
44
63
|
const debtRows = data?.debt ?? EMPTY_DEBT;
|
|
45
64
|
const activeRows = tab === "cash" ? cashRows : debtRows;
|
|
46
65
|
const recordDate = tab === "cash" ? data?.cash.recordDate ?? null : debtRows[0]?.date ?? null;
|
|
66
|
+
const [storedRange, setStoredRange] = usePluginPaneState<string>("range", "1Y");
|
|
67
|
+
const range = tgaRange(storedRange);
|
|
68
|
+
const resetChartView = useChartViewReset();
|
|
47
69
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
48
70
|
const resolvedId = activeRows.some((row) => row.id === selectedId) ? selectedId : activeRows[0]?.id ?? null;
|
|
49
71
|
const info = useMemo(
|
|
@@ -58,13 +80,46 @@ export function TreasuryDailyPane({ width, height, focused }: PaneProps) {
|
|
|
58
80
|
dense: true,
|
|
59
81
|
});
|
|
60
82
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
61
|
-
|
|
83
|
+
const refresh = useCallback(() => void resource.reload(), [resource.reload]);
|
|
84
|
+
usePaneRefreshKey(refresh, { focused });
|
|
62
85
|
usePaneStatusFooter({
|
|
63
86
|
registrationId: TREASURY_DAILY_PANE_ID,
|
|
64
87
|
loading: resource.loading,
|
|
65
88
|
error: resource.error,
|
|
66
89
|
info,
|
|
67
90
|
});
|
|
91
|
+
usePaneNoticeFooter({
|
|
92
|
+
registrationId: `${TREASURY_DAILY_PANE_ID}:history`,
|
|
93
|
+
focused: focused && tab === "cash",
|
|
94
|
+
notices: data?.tgaError ? [`Closing balance history is unavailable: ${data.tgaError}`] : [],
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// The closing balance the table's last line states for the day, drawn over the range chosen.
|
|
98
|
+
const series = useMemo(() => {
|
|
99
|
+
const closes = tgaClosesInRange(data?.tga ?? [], range);
|
|
100
|
+
return closes.length < 2 ? [] : [staticSeries(
|
|
101
|
+
closes.map((close) => scalarPoint(new Date(`${close.date}T00:00:00Z`), close.millions / 1_000)),
|
|
102
|
+
{ id: "tga-closing-balance", label: TGA_SERIES_LABEL, color: colors.positive, calendarSpaced: true },
|
|
103
|
+
)];
|
|
104
|
+
}, [colors.positive, data?.tga, range]);
|
|
105
|
+
const chart: ChartTableChart | null = data?.tgaError ? null : {
|
|
106
|
+
series,
|
|
107
|
+
formatValue: formatBillionsLevel,
|
|
108
|
+
formatAxisValue: formatBillionsAxis,
|
|
109
|
+
remoteKind: "treasury-general-account",
|
|
110
|
+
empty: "No closing balance history.",
|
|
111
|
+
};
|
|
112
|
+
// The number keys pick the range, as the bar's hints say; picking the one showing sends the chart back to it.
|
|
113
|
+
const handleKeyDown = useCallback((event: DataTableKeyEvent) => {
|
|
114
|
+
if (handleRefreshKey(event, refresh, { stopPropagation: true })) return true;
|
|
115
|
+
if (!isPlainKeyboardEvent(event)) return false;
|
|
116
|
+
const picked = TGA_RANGES.find((option) => option.hint === event.name);
|
|
117
|
+
if (!picked) return false;
|
|
118
|
+
event.preventDefault?.();
|
|
119
|
+
if (picked.value === range) resetChartView();
|
|
120
|
+
else setStoredRange(picked.value);
|
|
121
|
+
return true;
|
|
122
|
+
}, [range, refresh, resetChartView, setStoredRange]);
|
|
68
123
|
|
|
69
124
|
const bodyHeight = Math.max(1, height - tabRows);
|
|
70
125
|
const renderCash = useCallback((row: CashAccount, column: CashColumn): DataTableCell => {
|
|
@@ -91,6 +146,30 @@ export function TreasuryDailyPane({ width, height, focused }: PaneProps) {
|
|
|
91
146
|
focused={focused}
|
|
92
147
|
rootWidth={width}
|
|
93
148
|
rootHeight={bodyHeight}
|
|
149
|
+
rootBefore={chart ? (
|
|
150
|
+
<ChartTableHeader
|
|
151
|
+
width={width}
|
|
152
|
+
height={bodyHeight}
|
|
153
|
+
tableRows={cashRows.length}
|
|
154
|
+
tableColumns={CASH_COLUMNS}
|
|
155
|
+
query={(
|
|
156
|
+
<QueryBar
|
|
157
|
+
width={width}
|
|
158
|
+
filters={[{
|
|
159
|
+
id: "range",
|
|
160
|
+
label: "Range",
|
|
161
|
+
inline: true,
|
|
162
|
+
chartRange: true,
|
|
163
|
+
value: range,
|
|
164
|
+
options: RANGE_OPTIONS,
|
|
165
|
+
onChange: (value: string) => setStoredRange(tgaRange(value) satisfies TgaRange),
|
|
166
|
+
}]}
|
|
167
|
+
/>
|
|
168
|
+
)}
|
|
169
|
+
chart={chart}
|
|
170
|
+
/>
|
|
171
|
+
) : undefined}
|
|
172
|
+
onRootKeyDown={handleKeyDown}
|
|
94
173
|
columns={CASH_COLUMNS}
|
|
95
174
|
items={cashRows}
|
|
96
175
|
sortColumnId={null}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
|
+
import { createTtlCache } from "../../../data/ttl-cache";
|
|
2
3
|
import { getSharedMarketDataCoordinator, MarketDataCoordinator, resolveEntryValue } from "../../../market-data/coordinator";
|
|
3
4
|
import type { InstrumentRef, OptionsRequest } from "../../../market-data/request-types";
|
|
4
5
|
import type { QueryEntry } from "../../../market-data/result-types";
|
|
@@ -73,19 +74,8 @@ export function withReusedYieldCurve(
|
|
|
73
74
|
dependencies: SurfaceLoaderDependencies,
|
|
74
75
|
reuseMs = TREASURY_CURVE_REUSE_MS,
|
|
75
76
|
): SurfaceLoaderDependencies {
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
...dependencies,
|
|
79
|
-
loadYieldCurve: () => {
|
|
80
|
-
const now = Date.now();
|
|
81
|
-
if (held && now - held.at < reuseMs) return held.curve;
|
|
82
|
-
const curve = dependencies.loadYieldCurve();
|
|
83
|
-
const entry = { at: now, curve };
|
|
84
|
-
held = entry;
|
|
85
|
-
curve.catch(() => { if (held === entry) held = null; });
|
|
86
|
-
return curve;
|
|
87
|
-
},
|
|
88
|
-
};
|
|
77
|
+
const curves = createTtlCache<YieldPoint[]>({ ttlMs: reuseMs });
|
|
78
|
+
return { ...dependencies, loadYieldCurve: () => curves.load("curve", () => dependencies.loadYieldCurve()) };
|
|
89
79
|
}
|
|
90
80
|
|
|
91
81
|
export interface SurfaceLoadRequest {
|
|
@@ -12,7 +12,7 @@ import { createSurfaceDependencies, loadVolatilitySurface } from "./client";
|
|
|
12
12
|
import { buildSurfaceGrid, type SurfaceGridOptions, type SurfaceSettings, type SurfaceSnapshot } from "./model";
|
|
13
13
|
import { atCapturePrecision, formatCaptureTime, loadDatedSurface, storedSurfaceDate, type DatedSurfaceSnapshot } from "./stored";
|
|
14
14
|
import { listingFateEmpty, loadListingFate } from "../../../tickers/listing-fate";
|
|
15
|
-
import { coinOptionsAlias, coinWithoutOptionsRefusal } from "../../../market-data/options-alternatives";
|
|
15
|
+
import { coinOptionsAlias, coinWithoutOptionsRefusal, fxVolatilityRefusal } from "../../../market-data/options-alternatives";
|
|
16
16
|
import { formatUtcTime } from "../../../utils/utc-time";
|
|
17
17
|
import { dvolCurrency, dvolGap, dvolSeriesId, loadDvol, type DvolPayload } from "../volatility/dvol";
|
|
18
18
|
import {
|
|
@@ -118,7 +118,7 @@ function skewSection(snapshot: SurfaceSnapshot): HeadlessBundleSection {
|
|
|
118
118
|
return { title: "Skew", columns: [
|
|
119
119
|
{ key: "expiry", header: "Expiry" }, { key: "days", header: "Days", format: days },
|
|
120
120
|
{ key: "put25", header: "25D put IV", format: iv }, { key: "call25", header: "25D call IV", format: iv },
|
|
121
|
-
// Each difference names its sign: the risk reversal is call minus put
|
|
121
|
+
// Each difference names its sign: the risk reversal is call minus put here and in VCA and HIVG's rr25d.
|
|
122
122
|
{ key: "riskReversal", header: skewHeader("RR", "call-put"), format: gap }, { key: "butterfly", header: skewHeader("BF", "wings-ATM"), format: gap },
|
|
123
123
|
{ key: "moneynessSkew", header: skewHeader("90/110", "90%-110%"), format: (value, row) => typeof value === "number" ? points(value)
|
|
124
124
|
: (row as unknown as SurfaceSkewRow).moneynessGap ?? "--" },
|
|
@@ -169,7 +169,7 @@ export const volSurfaceHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
169
169
|
shape: "bundle", argument: { kind: "ticker", description: "Underlying ticker" },
|
|
170
170
|
usListingsOnly: "Options data",
|
|
171
171
|
symbolAlias: coinOptionsAlias("OVDV"),
|
|
172
|
-
refuseSymbol: coinWithoutOptionsRefusal("OVDV"),
|
|
172
|
+
refuseSymbol: (key) => coinWithoutOptionsRefusal("OVDV")(key) ?? fxVolatilityRefusal("OVDV")(key),
|
|
173
173
|
discovery: { suggestFor: IMPLIED_VOL_WORDS },
|
|
174
174
|
describe: (args) => `OVDV ${args.symbols[0] ?? ""}`,
|
|
175
175
|
options: [
|
|
@@ -39,7 +39,7 @@ export const volSurfaceModule: PluginModule = {
|
|
|
39
39
|
paneTemplates: [createTickerSurfacePaneTemplate({
|
|
40
40
|
id: "vol-surface-pane", paneId: "vol-surface", label: "Volatility Surface",
|
|
41
41
|
description: "Implied volatility surface, smiles, term structure, skew (25-delta risk reversal and butterfly) and forwards; DVOL beside BTC-USD and ETH-USD.",
|
|
42
|
-
keywords: ["ovdv", "volatility", "surface", "smile", "skew", "options", "risk reversal", "butterfly", "25 delta", "dvol", "deribit", "implied volatility index"],
|
|
42
|
+
keywords: ["ovdv", "volatility", "vol", "surface", "smile", "skew", "options", "risk reversal", "butterfly", "25 delta", "dvol", "deribit", "implied volatility index"],
|
|
43
43
|
shortcut: "OVDV", publicShare: true,
|
|
44
44
|
settings: (_symbol, _context, options) => options?.values?.expiration
|
|
45
45
|
? { expiration: Number(options.values.expiration) } : {},
|
|
@@ -43,7 +43,7 @@ import { expiryLabel, formatIv, formatPrice, formatVolPoints, HIDDEN_SLOPE_TEXT,
|
|
|
43
43
|
surfaceSkewRow, surfaceSpotBasisLabel } from "./tables";
|
|
44
44
|
import { findListedExpiry, unlistedExpiryText } from "../../../utils/option-expiry";
|
|
45
45
|
import { dvolCurrency, dvolGap, loadDvol, type DvolPayload } from "../volatility/dvol";
|
|
46
|
-
import { COIN_OPTIONS_HINT, isCoinWithoutOptions, optionsUnavailableTitle } from "../../../market-data/options-alternatives";
|
|
46
|
+
import { COIN_OPTIONS_HINT, fxVolatilityNote, isCoinWithoutOptions, optionsUnavailableTitle } from "../../../market-data/options-alternatives";
|
|
47
47
|
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
48
48
|
import { surfaceThirtyDayAtm } from "./tables";
|
|
49
49
|
|
|
@@ -191,9 +191,10 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
191
191
|
const fateExchange = target?.effectiveExchange ?? "";
|
|
192
192
|
const fateLoader = useCallback(async () => (await getSharedMarketDataCoordinator()
|
|
193
193
|
?.loadListingFate({ symbol: underlying, exchange: fateExchange }))?.note ?? null, [underlying, fateExchange]);
|
|
194
|
-
// A coin nothing lists options on (SOL-USD) says which coins have them.
|
|
194
|
+
// A coin nothing lists options on (SOL-USD) says which coins have them, and an FX pair which funds stand in.
|
|
195
195
|
const coinNote = underlying && isCoinWithoutOptions(underlying)
|
|
196
|
-
? `${optionsUnavailableTitle(parsePublicTickerKey(underlying).symbol)} ${COIN_OPTIONS_HINT}`
|
|
196
|
+
? `${optionsUnavailableTitle(parsePublicTickerKey(underlying).symbol)} ${COIN_OPTIONS_HINT}`
|
|
197
|
+
: underlying ? fxVolatilityNote(underlying, target?.effectiveExchange) : null;
|
|
197
198
|
const fateNote = useAsyncResource(underlying && noChain && !coinNote ? fateLoader : null).data ?? coinNote;
|
|
198
199
|
const ivRank = useIvRank(underlying || null);
|
|
199
200
|
// A coin's options (BTC-USD, ETH-USD) come with Deribit's volatility index.
|
|
@@ -224,9 +225,10 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
224
225
|
// visible; the upstream chain cache dedupes the requests across users. A
|
|
225
226
|
// delayed surface keeps the configured refresh.
|
|
226
227
|
const realtimeSurface = !!settledSnapshot?.expiries.some((entry) => entry.dataSource === "live" || entry.realtimeEligible === true);
|
|
227
|
-
const
|
|
228
|
+
const liveRefresh = useLiveSessionRefresh(reloadLive, SURFACE_LIVE_RELOAD_MS,
|
|
228
229
|
!!symbol && spotAvailable && !historyDate && !fallbackDate && liveStreaming && realtimeSurface && !resource.loading,
|
|
229
230
|
resource.updatedAt);
|
|
231
|
+
const liveActive = liveRefresh.live;
|
|
230
232
|
useAutoRefresh(resource.updatedAt, liveActive ? noRefresh : resource.load);
|
|
231
233
|
const shownDate = historyDate ?? fallbackDate;
|
|
232
234
|
const storedLoader = useCallback(async () => storedSurfaceSnapshot(await loadStoredSurface(underlying, shownDate!)), [underlying, shownDate]);
|
|
@@ -396,7 +398,7 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
396
398
|
...(pinned != null && snapshot && !resource.loading && !snapshot.catalogue.includes(pinned)
|
|
397
399
|
&& !snapshot.failures.some((failure) => failure.expiration === pinned)
|
|
398
400
|
? [`${expiryLabel(pinned)}: ${unlistedExpiryText(pinned, snapshot.catalogue)}`] : []),
|
|
399
|
-
...(active.error ? [active.error] : [])];
|
|
401
|
+
...(active.error ? [active.error] : []), ...(liveRefresh.error ? [liveRefresh.error] : [])];
|
|
400
402
|
const spotBasis = snapshot && !snapshot.stored ? surfaceSpotBasisLabel(snapshot) : null;
|
|
401
403
|
// What the quotes are and when they were observed; a reload moves the time.
|
|
402
404
|
const freshness = !snapshot ? null : snapshot.stored ? `stored close ${snapshot.stored.sessionDate}`
|
|
@@ -14,13 +14,13 @@ export const volatilityModule: PluginModule = {
|
|
|
14
14
|
paneTemplates: [{
|
|
15
15
|
id: "volatility-term-structure-pane", paneId: "volatility-term-structure",
|
|
16
16
|
label: "VIX Curve", description: "Dated VIX tenor closes, the 3M/30D ratio history and the cross-asset volatility board.",
|
|
17
|
-
keywords: ["vix", "volatility", "curve", "contango", "backwardation", "vvix", "skew", "move"],
|
|
17
|
+
keywords: ["vix", "volatility", "vol", "curve", "contango", "backwardation", "vvix", "skew", "move"],
|
|
18
18
|
shortcut: { prefix: "VIX" }, headless: volatilityHeadless,
|
|
19
19
|
createInstance: () => ({ instanceId: "volatility:curve", title: "VIX", placement: "floating", settings: { initialTab: "curve" } }),
|
|
20
20
|
}, {
|
|
21
21
|
id: "volatility-board-pane", paneId: "volatility-term-structure",
|
|
22
22
|
label: "Cross-asset Volatility", description: "Volatility index closes, daily changes and one-year percentiles.",
|
|
23
|
-
keywords: ["volatility", "cross asset", "vols", "vxn", "ovx", "gvz"],
|
|
23
|
+
keywords: ["volatility", "vol", "cross asset", "vols", "vxn", "ovx", "gvz"],
|
|
24
24
|
shortcut: { prefix: "VOLS" }, headless: volatilityHeadless,
|
|
25
25
|
createInstance: () => ({ instanceId: "volatility:board", title: "VOLS", placement: "floating", settings: { initialTab: "board" } }),
|
|
26
26
|
}],
|
|
@@ -107,7 +107,7 @@ export function VolatilityPane({ focused, width, height }: PaneProps) {
|
|
|
107
107
|
await coordinator.loadQuotesBatch(quiet.map((target) => ({ symbol: target.symbol, exchange: "" })), { forceRefresh: true });
|
|
108
108
|
}, [liveTargets]);
|
|
109
109
|
// With streaming off the quote layer already polls every minute.
|
|
110
|
-
useLiveSessionRefresh(refreshIndexLevels, INDEX_LEVEL_REFRESH_MS, liveStreaming && liveTargets.length > 0);
|
|
110
|
+
const levelRefresh = useLiveSessionRefresh(refreshIndexLevels, INDEX_LEVEL_REFRESH_MS, liveStreaming && liveTargets.length > 0);
|
|
111
111
|
const liveLevels = useThrottledValue(useMemo(() => {
|
|
112
112
|
const levels = new Map<VolatilityIndexId, VolatilityLiveLevel>();
|
|
113
113
|
for (const definition of VOLATILITY_INDICES) {
|
|
@@ -139,7 +139,7 @@ export function VolatilityPane({ focused, width, height }: PaneProps) {
|
|
|
139
139
|
}, [data?.board, sort, loading]);
|
|
140
140
|
const selected = rows.find((row) => row.id === selectedId) ?? rows[0] ?? null;
|
|
141
141
|
useVolatilityEvidence(result, tab, selected, resource.loading);
|
|
142
|
-
const notices = [resource.error, ...(result?.errors ?? []), ...(data?.warnings ?? []),
|
|
142
|
+
const notices = [resource.error, levelRefresh.error, ...(result?.errors ?? []), ...(data?.warnings ?? []),
|
|
143
143
|
...(selected?.warnings ?? [])].filter((notice): notice is string => !!notice);
|
|
144
144
|
usePaneNoticeFooter({ registrationId: "volatility-notices", notices, focused });
|
|
145
145
|
// From the tab the press finds, so the footer's hint never holds an old one.
|
|
@@ -84,15 +84,15 @@ function layerColumns(layer: GeoLayerInfo): HeadlessPaneColumn[] {
|
|
|
84
84
|
/**
|
|
85
85
|
* What a search that finds nothing says. A search outside a one-country layer
|
|
86
86
|
* finds nothing because the layer has nothing there; ships are found by name,
|
|
87
|
-
*
|
|
88
|
-
*
|
|
87
|
+
* or by the name or ticker of an operator whose fleet is linked, so anything
|
|
88
|
+
* else (or a server that links no fleet) gets a name to try.
|
|
89
89
|
*/
|
|
90
90
|
function emptySearchNote(layer: GeoLayerInfo, query: string): string | null {
|
|
91
91
|
const typed = query.trim();
|
|
92
92
|
if (!typed) return null;
|
|
93
93
|
const coverage = layerCoverageNote(layer);
|
|
94
94
|
if (coverage) return `No ${layer.name.toLowerCase()} match "${typed}". ${coverage}`;
|
|
95
|
-
if (layer.id === "vessels") return `No ${layer.name.toLowerCase()} match "${typed}"; try a ship-name prefix, such as --query Norwegian.`;
|
|
95
|
+
if (layer.id === "vessels") return `No ${layer.name.toLowerCase()} match "${typed}"; try a ship-name prefix or an operator, such as --query Norwegian or --query "Royal Caribbean".`;
|
|
96
96
|
return null;
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -75,7 +75,8 @@ const NATIVE_TARGETS: PluginTarget[] = ["cli", "tui", "desktop"];
|
|
|
75
75
|
*/
|
|
76
76
|
function functionsOf(plugin: GloomPlugin) {
|
|
77
77
|
return pluginFunctions({
|
|
78
|
-
|
|
78
|
+
// An unlisted function (`listed: false`) is not published.
|
|
79
|
+
templates: (plugin.paneTemplates ?? []).filter((template) => template.listed !== false).map((template) => ({
|
|
79
80
|
label: template.label,
|
|
80
81
|
prefix: template.shortcut?.prefix,
|
|
81
82
|
description: template.description,
|
|
@@ -186,6 +186,19 @@ function repoDirectory(repo: string | undefined): string | null {
|
|
|
186
186
|
return name ? name.toLowerCase() : null;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Built-ins write their copy as sentences and registry plugins as labels, so a
|
|
191
|
+
* row's short line is read without its closing period, whoever wrote it, and
|
|
192
|
+
* the detail's paragraph always has one. An ellipsis stays as written.
|
|
193
|
+
*/
|
|
194
|
+
function rowLine(text: string | undefined): string {
|
|
195
|
+
return (text ?? "").trim().replace(/(?<!\.)\.$/, "");
|
|
196
|
+
}
|
|
197
|
+
function detailParagraph(text: string | undefined): string | undefined {
|
|
198
|
+
const paragraph = text?.trim();
|
|
199
|
+
return paragraph ? /[\p{L}\p{N}]$/u.test(paragraph) ? `${paragraph}.` : paragraph : undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
189
202
|
/** The short line a built-in's row reads: its editorial tagline, else its description. */
|
|
190
203
|
function builtinTagline(local: InstalledPlugin): string {
|
|
191
204
|
return BUILTIN_EDITORIAL[local.id]?.tagline ?? local.description ?? "";
|
|
@@ -257,8 +270,8 @@ export function mergeCatalog(options: {
|
|
|
257
270
|
entries.push({
|
|
258
271
|
id: plugin.id,
|
|
259
272
|
name: builtin?.name ?? plugin.name,
|
|
260
|
-
tagline: builtin ? builtinTagline(builtin) : plugin.tagline,
|
|
261
|
-
description: builtin?.description ?? plugin.description,
|
|
273
|
+
tagline: rowLine(builtin ? builtinTagline(builtin) : plugin.tagline),
|
|
274
|
+
description: detailParagraph(builtin?.description ?? plugin.description),
|
|
262
275
|
categories: builtin ? BUILTIN_EDITORIAL[plugin.id]?.categories ?? plugin.categories : plugin.categories,
|
|
263
276
|
tier: plugin.tier,
|
|
264
277
|
targets: plugin.targets,
|
|
@@ -309,10 +322,10 @@ export function mergeCatalog(options: {
|
|
|
309
322
|
entries.push({
|
|
310
323
|
id: local.id,
|
|
311
324
|
name: local.name,
|
|
312
|
-
tagline: local.source === "builtin"
|
|
325
|
+
tagline: rowLine(local.source === "builtin"
|
|
313
326
|
? builtinTagline(local)
|
|
314
|
-
: local.description ?? (local.linked ? "Linked from a local checkout" : "Installed outside the registry"),
|
|
315
|
-
description: local.description,
|
|
327
|
+
: local.description ?? (local.linked ? "Linked from a local checkout" : "Installed outside the registry")),
|
|
328
|
+
description: detailParagraph(local.description),
|
|
316
329
|
categories: local.source === "builtin" ? [...BUILTIN_EDITORIAL[local.id]?.categories ?? []] : ["unlisted"],
|
|
317
330
|
// Shipped by Gloom like every built-in, whether or not the feed lists it.
|
|
318
331
|
tier: local.source === "builtin" ? "official" : "community",
|
|
@@ -36,10 +36,12 @@ export interface FuturesSettlement {
|
|
|
36
36
|
previous: number | null;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
39
|
+
/** A future's contract size, settlement and front reference for collection rows. */
|
|
40
40
|
export interface PortfolioFuturesTerms {
|
|
41
41
|
size: FuturesContractSize | null;
|
|
42
42
|
settlement: FuturesSettlement | null;
|
|
43
|
+
/** A different contract that is currently front, when the terms name one. */
|
|
44
|
+
front?: Pick<NonNullable<FuturesContractTermsPayload["front"]>, "contract" | "contractSymbol">;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
/** A grain or a soft quotes in US cents, so a dollar point value is a hundred of its price units. */
|
|
@@ -167,7 +169,7 @@ export function previousFuturesSettlement(
|
|
|
167
169
|
|
|
168
170
|
/** What a portfolio reads from a future's terms, with the settlement before the latest when the history has it. */
|
|
169
171
|
export function portfolioFuturesTerms(
|
|
170
|
-
terms: Pick<FuturesContractTermsPayload, "spec" | "settlement"> | null | undefined,
|
|
172
|
+
terms: (Pick<FuturesContractTermsPayload, "spec" | "settlement"> & Partial<Pick<FuturesContractTermsPayload, "front" | "contract">>) | null | undefined,
|
|
171
173
|
history?: readonly PricePoint[] | null,
|
|
172
174
|
): PortfolioFuturesTerms {
|
|
173
175
|
const settlement = terms?.settlement && Number.isFinite(terms.settlement.price) && terms.settlement.price > 0
|
|
@@ -177,7 +179,8 @@ export function portfolioFuturesTerms(
|
|
|
177
179
|
previous: previousFuturesSettlement(history, terms.settlement.date, terms.settlement.price),
|
|
178
180
|
}
|
|
179
181
|
: null;
|
|
180
|
-
|
|
182
|
+
const front = terms?.front && terms.front.contract !== terms.contract ? terms.front : undefined;
|
|
183
|
+
return { size: futuresContractSize(terms?.spec), settlement, ...(front ? { front } : {}) };
|
|
181
184
|
}
|
|
182
185
|
|
|
183
186
|
/**
|
|
@@ -23,21 +23,27 @@ function tradesInWholeUnits(ticker: TickerRecord): boolean {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Why a typed size cannot be stored as read: a dot and three digits (`1.
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* command asks which, as it does for `1,250`.
|
|
30
|
-
*
|
|
26
|
+
* Why a typed size cannot be stored as read: a dot and three digits (`1.000`,
|
|
27
|
+
* `1.250`), which is as likely the thousands grouping many locales write
|
|
28
|
+
* (1,000 shares, as cash reads `1.000`) as a fraction of a share, so the
|
|
29
|
+
* command asks which, as it does for `1,250`. On a listing held in whole
|
|
30
|
+
* units the thousands reading is the only one that can be held, and the
|
|
31
|
+
* message says so. Null for any other size (`0.500`, `12.25`).
|
|
31
32
|
*/
|
|
32
|
-
export function
|
|
33
|
+
export function ambiguousQuantityProblem(
|
|
33
34
|
typed: string,
|
|
34
35
|
ticker: TickerRecord,
|
|
35
36
|
options: { label?: PositionQuantityLabel; listing?: string } = {},
|
|
36
37
|
): string | null {
|
|
37
38
|
const reading = thousandsDotReadings(typed);
|
|
38
|
-
if (!reading
|
|
39
|
+
if (!reading) return null;
|
|
39
40
|
const label = options.label ?? positionQuantityLabel(ticker);
|
|
40
41
|
const listing = options.listing ?? ticker.metadata.ticker;
|
|
41
|
-
const because =
|
|
42
|
-
|
|
42
|
+
const because = tradesInWholeUnits(ticker)
|
|
43
|
+
? `${listing} trades in whole ${label === "Contracts" ? "contracts" : "shares"}`
|
|
44
|
+
: undefined;
|
|
45
|
+
// The fraction typed so it does not read two ways itself: 1.234 asks again, 1.2340 does not.
|
|
46
|
+
const fraction = String(reading.decimal);
|
|
47
|
+
const decimalAs = thousandsDotReadings(fraction) ? `${fraction}0` : fraction;
|
|
48
|
+
return `${label} ${ambiguousNumberMessage(typed, reading, { because, decimalAs })}.`;
|
|
43
49
|
}
|
package/src/remote/resources.ts
CHANGED
|
@@ -110,7 +110,8 @@ export function createRemoteResources({
|
|
|
110
110
|
}));
|
|
111
111
|
}
|
|
112
112
|
if (resource === "app://pane-templates") {
|
|
113
|
-
|
|
113
|
+
// An unlisted function (`listed: false`) is left out, as of every list.
|
|
114
|
+
return [...pluginRegistry.paneTemplates.values()].filter((template) => template.listed !== false).map((template) => ({
|
|
114
115
|
id: template.id,
|
|
115
116
|
paneId: template.paneId,
|
|
116
117
|
label: template.label,
|
|
@@ -457,6 +457,32 @@ function WebDataTableRowInner<
|
|
|
457
457
|
|
|
458
458
|
function CellText({ cell, column, color }: { cell: DataTableCell; column: DataTableColumn; color: string }) {
|
|
459
459
|
const style = clippedCellTextStyle(column, color, cell.attributes ?? TextAttributes.NONE);
|
|
460
|
+
if (cell.note) {
|
|
461
|
+
const text = (
|
|
462
|
+
<div
|
|
463
|
+
style={{
|
|
464
|
+
display: "flex",
|
|
465
|
+
flexWrap: "wrap",
|
|
466
|
+
alignContent: "flex-start",
|
|
467
|
+
justifyContent: contentJustifyForAlign(column.align),
|
|
468
|
+
columnGap: "var(--gloom-space-sm)",
|
|
469
|
+
width: "100%",
|
|
470
|
+
height: "100%",
|
|
471
|
+
minWidth: 0,
|
|
472
|
+
overflow: "hidden",
|
|
473
|
+
}}
|
|
474
|
+
>
|
|
475
|
+
<span title={cell.text} style={{ ...style, width: "auto", flex: "0 1 auto", minWidth: 0 }}>{cell.text}</span>
|
|
476
|
+
<span data-gloom-role="data-table-cell-note" style={{ ...cellTextStyle(CSS_TEXT_DIM, TextAttributes.NONE), flex: "none" }}>{cell.note}</span>
|
|
477
|
+
</div>
|
|
478
|
+
);
|
|
479
|
+
return cell.leading === undefined ? text : (
|
|
480
|
+
<div style={{ display: "flex", alignItems: "center", gap: LABEL_GAP_PX, width: "100%", height: "100%", minWidth: 0, overflow: "hidden" }}>
|
|
481
|
+
<Leading>{cell.leading}</Leading>
|
|
482
|
+
{text}
|
|
483
|
+
</div>
|
|
484
|
+
);
|
|
485
|
+
}
|
|
460
486
|
if (cell.leading === undefined) return <span title={cell.text} style={style}>{cell.text}</span>;
|
|
461
487
|
return (
|
|
462
488
|
<div
|
|
@@ -27,6 +27,7 @@ function DefaultDesktopRow({
|
|
|
27
27
|
stacked: boolean;
|
|
28
28
|
}) {
|
|
29
29
|
const colors = useThemeColors();
|
|
30
|
+
if (item.kind === "heading") return <Text fg={colors.textMuted}>{item.label}</Text>;
|
|
30
31
|
return (
|
|
31
32
|
<Box
|
|
32
33
|
flexDirection="row"
|
|
@@ -142,6 +143,7 @@ export function WebListView({
|
|
|
142
143
|
const rowHoverBg = hoverBgColor ?? hoverBg(colors);
|
|
143
144
|
const selectedItem = selectedIndex >= 0 ? items[selectedIndex] : undefined;
|
|
144
145
|
const activeScrollIndex = scrollIndex ?? selectedIndex;
|
|
146
|
+
const headingAboveSelection = items[activeScrollIndex - 1]?.kind === "heading";
|
|
145
147
|
const rowStride = rowHeight + rowGap;
|
|
146
148
|
const tabStopIndex = selectedIndex >= 0 && items[selectedIndex] && !items[selectedIndex].disabled
|
|
147
149
|
? selectedIndex : items.findIndex((item) => !item.disabled);
|
|
@@ -176,14 +178,14 @@ export function WebListView({
|
|
|
176
178
|
if (!scrollBox) return;
|
|
177
179
|
const safeIndex = Math.min(activeScrollIndex, items.length - 1);
|
|
178
180
|
const viewportHeight = Math.max(scrollBox.viewport?.height ?? 0, 1);
|
|
179
|
-
const rowTop = safeIndex * rowStride;
|
|
180
|
-
const rowBottom =
|
|
181
|
+
const rowTop = Math.max(0, safeIndex - (headingAboveSelection ? 1 : 0)) * rowStride;
|
|
182
|
+
const rowBottom = safeIndex * rowStride + rowHeight;
|
|
181
183
|
if (rowTop < scrollBox.scrollTop) {
|
|
182
184
|
scrollBox.scrollTo(rowTop);
|
|
183
185
|
} else if (rowBottom > scrollBox.scrollTop + viewportHeight) {
|
|
184
186
|
scrollBox.scrollTo(rowBottom - viewportHeight);
|
|
185
187
|
}
|
|
186
|
-
}, [activeScrollIndex, autoScrollToIndex, items.length, rowHeight, rowStride, scrolls]);
|
|
188
|
+
}, [activeScrollIndex, autoScrollToIndex, headingAboveSelection, items.length, rowHeight, rowStride, scrolls]);
|
|
187
189
|
|
|
188
190
|
useEffect(() => {
|
|
189
191
|
if (!scrolls) return;
|
|
@@ -202,7 +204,7 @@ export function WebListView({
|
|
|
202
204
|
const list = document.getElementById(listDomId);
|
|
203
205
|
const active = document.activeElement;
|
|
204
206
|
if (!list || !active || !list.contains(active) || isEditableKeyboardTarget(active)) return;
|
|
205
|
-
const row = list.
|
|
207
|
+
const row = list.querySelector<HTMLElement>(`[data-gloom-list-index="${selectedIndex}"]`);
|
|
206
208
|
if (row && !row.contains(active)) row.focus({ preventScroll: scrolls });
|
|
207
209
|
}, [listDomId, scrolls, selectedIndex]);
|
|
208
210
|
|
|
@@ -246,10 +248,11 @@ export function WebListView({
|
|
|
246
248
|
onActivate?.(item, index);
|
|
247
249
|
}}
|
|
248
250
|
data-gloom-role="desktop-list-row"
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
aria-
|
|
252
|
-
|
|
251
|
+
data-gloom-list-index={index}
|
|
252
|
+
role={item.kind === "heading" ? "presentation" : "option"}
|
|
253
|
+
aria-selected={item.kind === "heading" ? undefined : selected}
|
|
254
|
+
aria-disabled={item.kind === "heading" ? undefined : disabled || undefined}
|
|
255
|
+
tabIndex={item.kind === "heading" ? undefined : index === tabStopIndex ? 0 : -1}
|
|
253
256
|
onKeyDown={(event: React.KeyboardEvent<HTMLElement>) => {
|
|
254
257
|
if (disabled) return;
|
|
255
258
|
if (event.key === " " || (event.key === "Enter" && !checkboxRows)) {
|
|
@@ -267,8 +270,8 @@ export function WebListView({
|
|
|
267
270
|
while (items[nextIndex]?.disabled) nextIndex += direction;
|
|
268
271
|
if (!items[nextIndex]) return;
|
|
269
272
|
onSelect?.(nextIndex);
|
|
270
|
-
const
|
|
271
|
-
|
|
273
|
+
const row = event.currentTarget.parentElement?.querySelector<HTMLElement>(`[data-gloom-list-index="${nextIndex}"]`);
|
|
274
|
+
row?.focus();
|
|
272
275
|
}}
|
|
273
276
|
style={listRowStyle(selected, disabled, colors, selection === "frame")}
|
|
274
277
|
>
|