gloomberb 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/types.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/fred-series.ts +15 -5
- package/src/cli/commands/market.ts +290 -159
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/overview.ts +31 -6
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +105 -52
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +31 -7
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +51 -16
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +22 -7
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +64 -16
- package/src/components/chart/composite/price-axis-labels.tsx +2 -1
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/command-bar/workflow/collection-actions.ts +4 -0
- package/src/components/index.ts +2 -0
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/onboarding/wizard-positions.ts +8 -0
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +57 -22
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/analytics/risk-client.ts +44 -11
- package/src/plugins/builtin/analytics/risk-fx.ts +61 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +10 -1
- package/src/plugins/builtin/analytics/risk-model.ts +79 -16
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/analytics/risk-test-data.ts +3 -0
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -3
- package/src/plugins/builtin/cloud/team/pane.tsx +30 -9
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/custom-view/loader.ts +3 -2
- package/src/plugins/builtin/custom-view/pane.tsx +5 -4
- package/src/plugins/builtin/custom-view/view-spec.ts +1 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +38 -2
- package/src/plugins/builtin/econ/fred-catalog.ts +6 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +12 -1
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/executives/data.ts +24 -8
- package/src/plugins/builtin/executives/index.tsx +2 -18
- package/src/plugins/builtin/executives/pane.tsx +34 -10
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +37 -16
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +2 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -3
- package/src/plugins/builtin/market-movers/session-body.tsx +27 -11
- package/src/plugins/builtin/market-movers/session.ts +25 -10
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +33 -13
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/letters/client.ts +1 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +6 -3
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +2 -1
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -1
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/feed-xml.ts +6 -1
- package/src/plugins/builtin/news/wire/rss/managed-feed.ts +1 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +33 -8
- package/src/plugins/builtin/notes/export.ts +49 -3
- package/src/plugins/builtin/notes/files.ts +14 -3
- package/src/plugins/builtin/notes/index.tsx +3 -3
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +45 -10
- package/src/plugins/builtin/notes/store.ts +13 -3
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -7
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +166 -15
- package/src/plugins/builtin/portfolio-list/cli/render.ts +69 -15
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +2 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +3 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +44 -17
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -1
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +42 -7
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/broker-reconnect.ts +105 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-feature-wall.tsx +9 -4
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/thirteenf/api.ts +4 -1
- package/src/plugins/builtin/thirteenf/data.ts +5 -1
- package/src/plugins/builtin/thirteenf/headless.ts +3 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +8 -3
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +61 -0
- package/src/portfolio/mutations.ts +16 -2
- package/src/portfolio/position-metrics.ts +45 -4
- package/src/portfolio/position-quantity.ts +43 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +39 -7
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +27 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +63 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/team/collections.ts +2 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/metadata.ts +10 -0
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/ticker.ts +9 -0
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +41 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
|
@@ -7,6 +7,7 @@ import { useThemeColors } from "../../theme/theme-context";
|
|
|
7
7
|
import { t } from "../../i18n";
|
|
8
8
|
import { Button, ListView, type ListViewItem } from "../ui";
|
|
9
9
|
import type { ButtonProps } from "../ui/button";
|
|
10
|
+
import { ListRowLines } from "../ui/list-row-lines";
|
|
10
11
|
import { TITLEBAR_OVERLAY_HEIGHT_PX } from "../layout/titlebar-overlay";
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -525,17 +526,14 @@ export function OnboardingChoiceList({
|
|
|
525
526
|
selectOnHover
|
|
526
527
|
renderRow={(item, state) => (
|
|
527
528
|
<Box flexDirection="row" alignItems="center" width="100%" minWidth={0}>
|
|
528
|
-
<Box flexDirection="
|
|
529
|
-
<
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
{
|
|
535
|
-
|
|
536
|
-
{item.description ? (
|
|
537
|
-
<Text fg={colors.textMuted} wrapText>{item.description}</Text>
|
|
538
|
-
) : null}
|
|
529
|
+
<Box flexDirection="row" flexGrow={1} minWidth={0}>
|
|
530
|
+
<ListRowLines
|
|
531
|
+
label={item.label}
|
|
532
|
+
description={item.description}
|
|
533
|
+
leading={item.leading}
|
|
534
|
+
labelColor={state.selected ? colors.textBright : colors.text}
|
|
535
|
+
labelBold={state.selected}
|
|
536
|
+
/>
|
|
539
537
|
</Box>
|
|
540
538
|
{item.detail ? (
|
|
541
539
|
<Text fg={colors.borderFocused} attributes={TextAttributes.BOLD} style={{ marginLeft: 10 }}>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createElement,
|
|
2
3
|
useCallback,
|
|
3
4
|
useEffect,
|
|
4
5
|
useMemo,
|
|
@@ -16,6 +17,7 @@ import { tf } from "../../i18n";
|
|
|
16
17
|
import type { useAppLanguage } from "../../i18n/react";
|
|
17
18
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
18
19
|
import type { ListViewItem } from "../ui";
|
|
20
|
+
import { LogoMark } from "../ui/logo-mark";
|
|
19
21
|
import type { PortfolioSub } from "./onboarding-steps";
|
|
20
22
|
import { brokerSetupGuideUrl } from "./portfolio-step/broker-setup-panel";
|
|
21
23
|
import type { useOnboardingBrokerSync } from "./wizard-broker-sync";
|
|
@@ -42,14 +44,15 @@ export function useOnboardingBrokerFields({
|
|
|
42
44
|
(): BrokerOption[] => getConnectableBrokerOptions(buildBrokerDirectory({
|
|
43
45
|
signedIn: signedInBrokers,
|
|
44
46
|
adapters: pluginRegistry.brokers.values(),
|
|
45
|
-
})),
|
|
46
|
-
[pluginRegistry.brokers, signedInBrokers],
|
|
47
|
+
}), pluginRegistry.getBrokerLogo),
|
|
48
|
+
[pluginRegistry.brokers, pluginRegistry.getBrokerLogo, signedInBrokers],
|
|
47
49
|
);
|
|
48
50
|
const brokerChoices = useMemo<ListViewItem[]>(() => brokerOptions.map((broker) => ({
|
|
49
51
|
id: broker.id,
|
|
50
52
|
label: tf("Connect {broker}", { broker: broker.name }),
|
|
51
53
|
// A broker offered two ways shows which way each choice connects.
|
|
52
54
|
description: broker.methodLabel ?? tf("Import positions from {broker}", { broker: broker.name }),
|
|
55
|
+
leading: createElement(LogoMark, { src: broker.logo, name: broker.name, size: "lead" }),
|
|
53
56
|
})), [brokerOptions, language]);
|
|
54
57
|
const activeBrokerFields = useMemo((): BrokerConfigField[] => {
|
|
55
58
|
if (!selectedBrokerId) return [];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { brokerMethodLabel, type BrokerDirectoryEntry } from "../../brokers/directory";
|
|
2
|
+
import { brokerDirectoryLogo, type BrokerPluginLogo } from "../../brokers/logos";
|
|
2
3
|
import type { SignedInBroker } from "../../brokers/signed-in/client";
|
|
3
4
|
import type { BrokerAdapter, BrokerPosition } from "../../types/broker";
|
|
4
5
|
import type { AppConfig, OnboardingProgress, OnboardingStage } from "../../types/config";
|
|
@@ -47,19 +48,25 @@ export type BrokerOption = {
|
|
|
47
48
|
name: string;
|
|
48
49
|
/** How this choice connects, when the same broker is also offered another way. */
|
|
49
50
|
methodLabel?: string;
|
|
51
|
+
/** The broker's logo for the desktop and the web (brokers/logos.ts); null draws its initial. */
|
|
52
|
+
logo?: string | null;
|
|
50
53
|
} & (
|
|
51
54
|
| { adapter: BrokerAdapter; signedIn?: undefined }
|
|
52
55
|
| { signedIn: SignedInBroker; adapter?: undefined }
|
|
53
56
|
);
|
|
54
57
|
|
|
55
|
-
export function getConnectableBrokerOptions(
|
|
58
|
+
export function getConnectableBrokerOptions(
|
|
59
|
+
directory: readonly BrokerDirectoryEntry[],
|
|
60
|
+
pluginLogo: BrokerPluginLogo = () => null,
|
|
61
|
+
): BrokerOption[] {
|
|
56
62
|
const options: BrokerOption[] = [];
|
|
57
63
|
for (const entry of directory) {
|
|
64
|
+
const logo = brokerDirectoryLogo(entry, pluginLogo);
|
|
58
65
|
for (const method of entry.methods) {
|
|
59
66
|
const methodLabel = entry.methods.length > 1 ? brokerMethodLabel(entry, method) : undefined;
|
|
60
67
|
options.push(method.kind === "signed-in"
|
|
61
|
-
? { id: `signed-in:${method.broker.id}`, name: entry.name, methodLabel, signedIn: method.broker }
|
|
62
|
-
: { id: method.adapter.id, name: method.adapter.name, methodLabel, adapter: method.adapter });
|
|
68
|
+
? { id: `signed-in:${method.broker.id}`, name: entry.name, methodLabel, logo, signedIn: method.broker }
|
|
69
|
+
: { id: method.adapter.id, name: method.adapter.name, methodLabel, logo, adapter: method.adapter });
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
72
|
return options;
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "../../portfolio/mutations";
|
|
13
13
|
import { isFuturesTicker } from "../../portfolio/futures-positions";
|
|
14
14
|
import { loadFuturesEntrySize } from "../../portfolio/futures-terms";
|
|
15
|
+
import { wholeUnitQuantityProblem } from "../../portfolio/position-quantity";
|
|
15
16
|
import { useAppDispatch, useAppSelector, useAppStateRef } from "../../state/app/context";
|
|
16
17
|
import { saveConfigImmediately } from "../../state/config-save-scheduler";
|
|
17
18
|
import { resolveTickerSearch, upsertTickerFromSearchResult, type ResolvedTickerSearch } from "../../tickers/search";
|
|
@@ -291,6 +292,13 @@ export function useOnboardingPositions({
|
|
|
291
292
|
if (shares === null) {
|
|
292
293
|
nextTicker = addTickerToPortfolio(ticker, portfolioId).ticker;
|
|
293
294
|
} else {
|
|
295
|
+
// 1.250 on a listing held in whole lots could be 1,250 as well, and asks as 1,250 does.
|
|
296
|
+
const lots = wholeUnitQuantityProblem(current.shares.trim(), ticker, { label: quantityLabel });
|
|
297
|
+
if (lots) {
|
|
298
|
+
setFieldIdx(1);
|
|
299
|
+
setError(lots);
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
294
302
|
const costBasis = avgCost ?? result.quote?.price;
|
|
295
303
|
if (costBasis === undefined || costBasis === null || !Number.isFinite(costBasis)) {
|
|
296
304
|
setFieldIdx(2);
|
|
@@ -13,9 +13,10 @@ import { useScopedButtonAction } from "./action-scope";
|
|
|
13
13
|
* still reads as a control next to a primary one. `plain` is text that happens
|
|
14
14
|
* to be clickable: no border, no fill. Anything shaped like a row, a table
|
|
15
15
|
* cell, a status bar chip or a link is `plain` — a box drawn around it reads as
|
|
16
|
-
* a control nested in the surface instead of part of it.
|
|
16
|
+
* a control nested in the surface instead of part of it. Use `quiet` for text
|
|
17
|
+
* actions that also need a filled selection/focus state without a frame.
|
|
17
18
|
*/
|
|
18
|
-
export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "plain";
|
|
19
|
+
export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "plain" | "quiet";
|
|
19
20
|
|
|
20
21
|
export interface ButtonProps {
|
|
21
22
|
label: string;
|
|
@@ -25,6 +26,8 @@ export interface ButtonProps {
|
|
|
25
26
|
expanded?: boolean;
|
|
26
27
|
onPress?: () => void;
|
|
27
28
|
variant?: ButtonVariant;
|
|
29
|
+
/** Semantic fill for a primary action, retained while keyboard focused. */
|
|
30
|
+
tone?: "positive" | "negative" | "neutral" | "warning";
|
|
28
31
|
disabled?: boolean;
|
|
29
32
|
/**
|
|
30
33
|
* The action it started is still running (a retry waiting for its answer).
|
|
@@ -56,13 +59,15 @@ export interface ButtonProps {
|
|
|
56
59
|
stopPropagation?: boolean;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
function resolveButtonColors(variant: ButtonVariant, active: boolean, disabled: boolean, colors: ThemeColors): { bg?: string; fg: string } {
|
|
62
|
+
function resolveButtonColors(variant: ButtonVariant, active: boolean, disabled: boolean, colors: ThemeColors, tone?: ButtonProps["tone"]): { bg?: string; fg: string } {
|
|
63
|
+
if (variant === "quiet") return { bg: active ? colors.selected : undefined, fg: disabled ? colors.textMuted : active ? colors.textBright : colors.textDim };
|
|
60
64
|
if (variant === "plain") {
|
|
61
65
|
return { fg: disabled ? colors.textMuted : active ? colors.textBright : colors.textDim };
|
|
62
66
|
}
|
|
63
67
|
if (disabled) {
|
|
64
68
|
return { bg: colors.panel, fg: colors.textMuted };
|
|
65
69
|
}
|
|
70
|
+
if (tone && variant === "primary") return { bg: colors[tone], fg: colors.bg };
|
|
66
71
|
if (active) {
|
|
67
72
|
return { bg: colors.selected, fg: colors.selectedText };
|
|
68
73
|
}
|
|
@@ -96,6 +101,7 @@ export function Button({
|
|
|
96
101
|
expanded,
|
|
97
102
|
onPress: pressAction,
|
|
98
103
|
variant = "secondary",
|
|
104
|
+
tone,
|
|
99
105
|
disabled = false,
|
|
100
106
|
pending = false,
|
|
101
107
|
pendingLabel,
|
|
@@ -138,6 +144,7 @@ export function Button({
|
|
|
138
144
|
expanded={expanded}
|
|
139
145
|
onPress={onPress}
|
|
140
146
|
variant={variant}
|
|
147
|
+
tone={tone}
|
|
141
148
|
disabled={inert}
|
|
142
149
|
pending={pending}
|
|
143
150
|
active={active}
|
|
@@ -152,7 +159,7 @@ export function Button({
|
|
|
152
159
|
);
|
|
153
160
|
}
|
|
154
161
|
|
|
155
|
-
const palette = resolveButtonColors(variant, active, inert, colors);
|
|
162
|
+
const palette = resolveButtonColors(variant, active, inert, colors, tone);
|
|
156
163
|
|
|
157
164
|
return (
|
|
158
165
|
<Box
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from "react";
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box, Text, useUiHost } from "../../ui";
|
|
3
3
|
import { type PromptContext, useDialogKeyboard } from "../../ui/dialog";
|
|
4
4
|
import { useThemeColors } from "../../theme/theme-context";
|
|
@@ -61,6 +61,9 @@ export function ChoiceDialog({
|
|
|
61
61
|
);
|
|
62
62
|
const selectedIndex = clampIndex(index, choices.length, -1);
|
|
63
63
|
const selectedChoice = selectedIndex >= 0 ? choices[selectedIndex] : undefined;
|
|
64
|
+
// Terminal input can deliver navigation and Enter before React renders.
|
|
65
|
+
const cursor = useRef(selectedIndex);
|
|
66
|
+
const selectIndex = (next: number) => { cursor.current = next; setIndex(next); };
|
|
64
67
|
const items = useMemo<ListViewItem[]>(() => choices.map((choice) => ({
|
|
65
68
|
id: choice.id,
|
|
66
69
|
label: choice.label,
|
|
@@ -72,7 +75,7 @@ export function ChoiceDialog({
|
|
|
72
75
|
const width = useMemo(() => choiceDialogWidth(title, choices), [choices, title]);
|
|
73
76
|
|
|
74
77
|
useEffect(() => {
|
|
75
|
-
|
|
78
|
+
selectIndex(clampIndex(cursor.current, choices.length, -1));
|
|
76
79
|
}, [choices.length]);
|
|
77
80
|
|
|
78
81
|
const activateChoice = (choice: ChoiceDialogChoice | undefined) => {
|
|
@@ -84,9 +87,9 @@ export function ChoiceDialog({
|
|
|
84
87
|
event.stopPropagation();
|
|
85
88
|
const move = listCursorMove(event, MAX_VISIBLE_CHOICE_ROWS - 1);
|
|
86
89
|
if (move) {
|
|
87
|
-
|
|
90
|
+
selectIndex(move(choices, cursor.current));
|
|
88
91
|
} else if (event.name === "enter" || event.name === "return") {
|
|
89
|
-
activateChoice(
|
|
92
|
+
activateChoice(choices[clampIndex(cursor.current, choices.length, -1)]);
|
|
90
93
|
} else if (event.name === "escape") {
|
|
91
94
|
resolve("");
|
|
92
95
|
}
|
|
@@ -106,7 +109,7 @@ export function ChoiceDialog({
|
|
|
106
109
|
height={Math.min(Math.max(items.length, 1), MAX_VISIBLE_CHOICE_ROWS)}
|
|
107
110
|
scrollable={items.length > MAX_VISIBLE_CHOICE_ROWS}
|
|
108
111
|
selectOnHover
|
|
109
|
-
onSelect={
|
|
112
|
+
onSelect={selectIndex}
|
|
110
113
|
onActivate={(_, nextIndex) => activateChoice(choices[nextIndex])}
|
|
111
114
|
/>
|
|
112
115
|
<Box height={1} />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { t } from "../../i18n";
|
|
3
3
|
import { blendHex, hoverBg } from "../../theme/colors";
|
|
4
|
+
import { blendForContrast, higherContrast } from "../../theme/color-utils";
|
|
4
5
|
import { useThemeColors } from "../../theme/theme-context";
|
|
5
6
|
import { Box, Text, TextAttributes, useUiCapabilities } from "../../ui";
|
|
6
7
|
import { truncateToDisplayWidth } from "../../utils/format";
|
|
@@ -62,6 +63,7 @@ export interface KeyValueRowProps {
|
|
|
62
63
|
width?: number;
|
|
63
64
|
labelWidth?: number;
|
|
64
65
|
emphasis?: boolean;
|
|
66
|
+
align?: "left" | "right";
|
|
65
67
|
/**
|
|
66
68
|
* Opens what the value names (a security's DES, a function): the whole row is the target, and inside a
|
|
67
69
|
* `PaneLinkMenu` the pane menu lists it as `link.label`, so the keyboard reaches what a click opens.
|
|
@@ -118,7 +120,7 @@ export function KeyValueRow(props: KeyValueRowProps) {
|
|
|
118
120
|
* width in characters, the value and detail taking the rest of the row and
|
|
119
121
|
* clipped in CSS where they run out of it.
|
|
120
122
|
*/
|
|
121
|
-
function NativeKeyValueRow({ label, value, detail, tone, color, width, labelWidth, emphasis = true, link }: KeyValueRowProps) {
|
|
123
|
+
function NativeKeyValueRow({ label, value, detail, tone, color, width, labelWidth, emphasis = true, align = "left", link }: KeyValueRowProps) {
|
|
122
124
|
const colors = useThemeColors();
|
|
123
125
|
usePaneLinkMenuEntry(link ? `key-value:${label}:${value}` : null, link?.label ?? "", () => link?.onOpen());
|
|
124
126
|
const target = link ? {
|
|
@@ -137,7 +139,7 @@ function NativeKeyValueRow({ label, value, detail, tone, color, width, labelWidt
|
|
|
137
139
|
<Text
|
|
138
140
|
fg={color ?? statToneColor(tone, colors) ?? colors.text}
|
|
139
141
|
attributes={emphasis ? TextAttributes.BOLD : undefined}
|
|
140
|
-
style={{ ...NATIVE_CLIP_STYLE, flex: detail ? "0 1 auto" : "1 1 auto" }}
|
|
142
|
+
style={{ ...NATIVE_CLIP_STYLE, flex: detail ? "0 1 auto" : "1 1 auto", textAlign: align }}
|
|
141
143
|
>
|
|
142
144
|
{value}
|
|
143
145
|
</Text>
|
|
@@ -146,7 +148,7 @@ function NativeKeyValueRow({ label, value, detail, tone, color, width, labelWidt
|
|
|
146
148
|
);
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
function TerminalKeyValueRow({ label, value, detail, tone, color, width, labelWidth, emphasis = true, link }: KeyValueRowProps) {
|
|
151
|
+
function TerminalKeyValueRow({ label, value, detail, tone, color, width, labelWidth, emphasis = true, align = "left", link }: KeyValueRowProps) {
|
|
150
152
|
const colors = useThemeColors();
|
|
151
153
|
usePaneLinkMenuEntry(link ? `key-value:${label}:${value}` : null, link?.label ?? "", () => link?.onOpen());
|
|
152
154
|
const target = link ? {
|
|
@@ -169,7 +171,7 @@ function TerminalKeyValueRow({ label, value, detail, tone, color, width, labelWi
|
|
|
169
171
|
<Box width={labelColumns} flexShrink={0} overflow="hidden">
|
|
170
172
|
<Text fg={colors.textDim}>{t(label)}</Text>
|
|
171
173
|
</Box>
|
|
172
|
-
<Box width={valueWidth} flexShrink={0} overflow="hidden">
|
|
174
|
+
<Box width={valueWidth} flexShrink={0} overflow="hidden" alignItems={align === "right" ? "flex-end" : undefined}>
|
|
173
175
|
<Text fg={color ?? statToneColor(tone, colors) ?? colors.text} attributes={emphasis ? TextAttributes.BOLD : undefined}>
|
|
174
176
|
{valueWidth === undefined ? value : truncateToDisplayWidth(value, valueWidth)}
|
|
175
177
|
</Text>
|
|
@@ -207,10 +209,13 @@ export function Badge({ label, tone = "neutral", color, variant = "subtle" }: Ba
|
|
|
207
209
|
const accent = color ?? (tone === "neutral" ? colors.textDim : tone === "accent" ? colors.borderFocused : colors[tone]);
|
|
208
210
|
const solid = variant === "solid";
|
|
209
211
|
const neutral = solid && tone === "neutral" && !color;
|
|
212
|
+
const background = neutral ? colors.selected : solid ? accent : blendHex(colors.bg, accent, BADGE_TINT);
|
|
213
|
+
const baseForeground = neutral ? colors.selectedText : solid ? colors.bg : accent;
|
|
214
|
+
const foreground = blendForContrast(baseForeground, background, higherContrast("#000000", "#ffffff", background), 4.5);
|
|
210
215
|
const chip = (
|
|
211
|
-
<Box height={nativePaneChrome ? undefined : 1} paddingX={1} backgroundColor={
|
|
216
|
+
<Box height={nativePaneChrome ? undefined : 1} paddingX={1} backgroundColor={background}
|
|
212
217
|
data-gloom-ui="badge" justifyContent={nativePaneChrome ? "center" : undefined} style={nativePaneChrome ? NATIVE_BADGE_STYLE : undefined}>
|
|
213
|
-
<Text fg={
|
|
218
|
+
<Text fg={foreground} attributes={TextAttributes.BOLD} style={nativePaneChrome ? NATIVE_BADGE_TEXT_STYLE : undefined}>{t(label)}</Text>
|
|
214
219
|
</Box>
|
|
215
220
|
);
|
|
216
221
|
return nativePaneChrome ? <Box height={1} flexShrink={0} justifyContent="center">{chip}</Box> : chip;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Box, Input, Span, Text, TextAttributes, useUiCapabilities, useUiHost } from "../../ui";
|
|
2
|
-
import { useEffect, useRef, useState, type ComponentType, type RefObject } from "react";
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState, type ComponentType, type RefObject } from "react";
|
|
3
3
|
import { type InputRenderable } from "../../ui";
|
|
4
4
|
import { useThemeColors } from "../../theme/theme-context";
|
|
5
5
|
import { useRemoteUiNode } from "../../remote/semantic-tree";
|
|
6
6
|
import { remoteStringValue } from "../../remote/semantic-helpers";
|
|
7
7
|
import { truncateWithEllipsis } from "../../utils/text-wrap";
|
|
8
|
+
import { Button } from "./button";
|
|
9
|
+
import { stepNumber } from "./number-step";
|
|
8
10
|
|
|
9
11
|
export interface FieldLabelProps {
|
|
10
12
|
label: string;
|
|
@@ -35,6 +37,8 @@ export function FieldLabel({ label, active = false, width, maxWidth }: FieldLabe
|
|
|
35
37
|
|
|
36
38
|
export interface TextFieldProps {
|
|
37
39
|
label?: string;
|
|
40
|
+
/** Accessible name when the visible label is rendered separately. */
|
|
41
|
+
accessibleLabel?: string;
|
|
38
42
|
/**
|
|
39
43
|
* The form's current field, in a form that walks its fields with the
|
|
40
44
|
* keyboard. The terminal marks its label with "> " and indents the other
|
|
@@ -51,6 +55,7 @@ export interface TextFieldProps {
|
|
|
51
55
|
/** The most characters it takes; typing and pasting stop there. */
|
|
52
56
|
maxLength?: number;
|
|
53
57
|
focused?: boolean;
|
|
58
|
+
disabled?: boolean;
|
|
54
59
|
width?: number;
|
|
55
60
|
inputRef?: RefObject<InputRenderable | null>;
|
|
56
61
|
onChange?: (value: string) => void;
|
|
@@ -60,7 +65,8 @@ export interface TextFieldProps {
|
|
|
60
65
|
type?: "text" | "password" | "date" | "email";
|
|
61
66
|
/** DOM autofill hint, e.g. "email" or "current-password"; terminal hosts ignore it. */
|
|
62
67
|
autoComplete?: string;
|
|
63
|
-
|
|
68
|
+
/** Well is a quiet filled field with an underline only while focused. */
|
|
69
|
+
variant?: "default" | "plain" | "well";
|
|
64
70
|
/**
|
|
65
71
|
* `comfortable` is the form-page size on the DOM renderer: a taller field
|
|
66
72
|
* with a tight label gap, for dialogs a user fills in rather than dense pane
|
|
@@ -117,7 +123,7 @@ function InlineTextField({ label = "", labelWidth = 0, active, width, hint, onMo
|
|
|
117
123
|
<Box flexDirection="column" width={width}>
|
|
118
124
|
<Box height={1} flexDirection="row" alignItems="center" gap={1} onMouseDown={onMouseDown}>
|
|
119
125
|
<FieldLabel label={label} active={active} width={labelWidth} />
|
|
120
|
-
<TextFieldControl {...props} width={inputWidth} onMouseDown={onMouseDown} />
|
|
126
|
+
<TextFieldControl {...props} accessibleLabel={props.accessibleLabel ?? label} width={inputWidth} onMouseDown={onMouseDown} />
|
|
121
127
|
</Box>
|
|
122
128
|
{hint ? (
|
|
123
129
|
<Box paddingLeft={labelWidth + 1}>
|
|
@@ -130,11 +136,13 @@ function InlineTextField({ label = "", labelWidth = 0, active, width, hint, onMo
|
|
|
130
136
|
|
|
131
137
|
function TextFieldControl({
|
|
132
138
|
label,
|
|
139
|
+
accessibleLabel,
|
|
133
140
|
active,
|
|
134
141
|
value,
|
|
135
142
|
placeholder,
|
|
136
143
|
maxLength,
|
|
137
144
|
focused,
|
|
145
|
+
disabled = false,
|
|
138
146
|
width,
|
|
139
147
|
inputRef,
|
|
140
148
|
onChange,
|
|
@@ -152,20 +160,20 @@ function TextFieldControl({
|
|
|
152
160
|
onKeyDown,
|
|
153
161
|
}: TextFieldProps) {
|
|
154
162
|
const colors = useThemeColors();
|
|
155
|
-
backgroundColor ??= colors.bg;
|
|
163
|
+
backgroundColor ??= variant === "well" ? colors.panel : colors.bg;
|
|
156
164
|
textColor ??= colors.text;
|
|
157
165
|
placeholderColor ??= colors.textDim;
|
|
158
166
|
useRemoteUiNode({
|
|
159
167
|
role: "text-field",
|
|
160
|
-
label: label ?? placeholder,
|
|
168
|
+
label: accessibleLabel ?? label ?? placeholder,
|
|
161
169
|
actions: {
|
|
162
170
|
setValue: (input) => {
|
|
163
171
|
const nextValue = remoteStringValue(input);
|
|
164
|
-
onChange?.(nextValue);
|
|
172
|
+
if (!disabled) onChange?.(nextValue);
|
|
165
173
|
},
|
|
166
174
|
submit: (input) => {
|
|
167
175
|
const nextValue = remoteStringValue(input, value ?? "");
|
|
168
|
-
onSubmit?.(nextValue);
|
|
176
|
+
if (!disabled) onSubmit?.(nextValue);
|
|
169
177
|
},
|
|
170
178
|
blur: () => onBlur?.(value ?? ""),
|
|
171
179
|
},
|
|
@@ -176,10 +184,12 @@ function TextFieldControl({
|
|
|
176
184
|
return (
|
|
177
185
|
<HostTextField
|
|
178
186
|
label={label}
|
|
187
|
+
accessibleLabel={accessibleLabel}
|
|
179
188
|
value={value}
|
|
180
189
|
placeholder={placeholder}
|
|
181
190
|
maxLength={maxLength}
|
|
182
|
-
focused={focused}
|
|
191
|
+
focused={focused && !disabled}
|
|
192
|
+
disabled={disabled}
|
|
183
193
|
width={width}
|
|
184
194
|
inputRef={inputRef}
|
|
185
195
|
onChange={onChange}
|
|
@@ -233,6 +243,7 @@ function TextFieldControl({
|
|
|
233
243
|
</Box>
|
|
234
244
|
)}
|
|
235
245
|
<Box height={1} onMouseDown={() => {
|
|
246
|
+
if (disabled) return;
|
|
236
247
|
onMouseDown?.();
|
|
237
248
|
resolvedInputRef.current?.focus?.();
|
|
238
249
|
}}>
|
|
@@ -243,16 +254,20 @@ function TextFieldControl({
|
|
|
243
254
|
selectable={!isPassword}
|
|
244
255
|
placeholder={isPassword ? "" : placeholder}
|
|
245
256
|
{...(maxLength === undefined ? {} : { maxLength })}
|
|
246
|
-
focused={focused}
|
|
257
|
+
focused={focused && !disabled}
|
|
247
258
|
textColor={isPassword ? backgroundColor : textColor}
|
|
248
259
|
placeholderColor={placeholderColor}
|
|
249
260
|
backgroundColor={backgroundColor}
|
|
250
261
|
selectionBg={isPassword ? backgroundColor : undefined}
|
|
251
262
|
selectionFg={isPassword ? backgroundColor : undefined}
|
|
252
263
|
showCursor={!isPassword}
|
|
253
|
-
onKeyDown={onKeyDown
|
|
264
|
+
onKeyDown={(event: Parameters<NonNullable<TextFieldProps["onKeyDown"]>>[0]) => {
|
|
265
|
+
if (disabled) { event.preventDefault(); event.stopPropagation(); return; }
|
|
266
|
+
onKeyDown?.(event);
|
|
267
|
+
}}
|
|
254
268
|
onCursorChange={() => syncCursorOffset()}
|
|
255
269
|
onInput={(nextValue: string) => {
|
|
270
|
+
if (disabled) return;
|
|
256
271
|
currentValueRef.current = nextValue;
|
|
257
272
|
syncCursorOffset(nextValue);
|
|
258
273
|
onChange?.(nextValue);
|
|
@@ -262,12 +277,12 @@ function TextFieldControl({
|
|
|
262
277
|
// the form has since cleared and swapped out still blurs when the
|
|
263
278
|
// next one takes focus, and passing its old text on would put it
|
|
264
279
|
// back into the cleared draft.
|
|
265
|
-
if (nextValue === currentValueRef.current) return;
|
|
280
|
+
if (disabled || nextValue === currentValueRef.current) return;
|
|
266
281
|
currentValueRef.current = nextValue;
|
|
267
282
|
syncCursorOffset(nextValue);
|
|
268
283
|
onChange?.(nextValue);
|
|
269
284
|
}}
|
|
270
|
-
onSubmit={() => onSubmit?.(currentValueRef.current)}
|
|
285
|
+
onSubmit={() => { if (!disabled) onSubmit?.(currentValueRef.current); }}
|
|
271
286
|
onBlur={() => onBlur?.(currentValueRef.current)}
|
|
272
287
|
/>
|
|
273
288
|
{isPassword && (
|
|
@@ -321,6 +336,10 @@ function sanitizeNumberInput(value: string, allowDecimal: boolean, allowNegative
|
|
|
321
336
|
export interface NumberFieldProps extends Omit<TextFieldProps, "onChange" | "onSubmit" | "onBlur"> {
|
|
322
337
|
allowDecimal?: boolean;
|
|
323
338
|
allowNegative?: boolean;
|
|
339
|
+
/** Opt-in shared minus/plus, pointer wheel and Up/Down stepping. Not an exchange tick unless the caller supplies one. */
|
|
340
|
+
step?: number;
|
|
341
|
+
min?: number;
|
|
342
|
+
max?: number;
|
|
324
343
|
onChange?: (value: string) => void;
|
|
325
344
|
onSubmit?: (value: string) => void;
|
|
326
345
|
onBlur?: (value: string) => void;
|
|
@@ -332,14 +351,75 @@ export function NumberField({
|
|
|
332
351
|
onChange,
|
|
333
352
|
onSubmit,
|
|
334
353
|
onBlur,
|
|
354
|
+
step,
|
|
355
|
+
min,
|
|
356
|
+
max,
|
|
335
357
|
...props
|
|
336
358
|
}: NumberFieldProps) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
359
|
+
const colors = useThemeColors();
|
|
360
|
+
const native = useUiCapabilities().nativePaneChrome;
|
|
361
|
+
const well = props.variant === "well";
|
|
362
|
+
const valueRef = useRef(props.value ?? "");
|
|
363
|
+
valueRef.current = props.value ?? "";
|
|
364
|
+
const localInput = useRef<InputRenderable | null>(null);
|
|
365
|
+
const inputRef = props.inputRef ?? localInput;
|
|
366
|
+
const stepping = step !== undefined;
|
|
367
|
+
const pendingCursor = useRef<number | undefined>(undefined);
|
|
368
|
+
useLayoutEffect(() => {
|
|
369
|
+
const cursor = pendingCursor.current, input = inputRef.current;
|
|
370
|
+
if (cursor === undefined || !input) return;
|
|
371
|
+
pendingCursor.current = undefined;
|
|
372
|
+
const offset = Math.min(cursor, (props.value ?? "").length);
|
|
373
|
+
if (input.setCursorOffset) input.setCursorOffset(offset);
|
|
374
|
+
else input.cursorOffset = offset;
|
|
375
|
+
}, [props.value, inputRef]);
|
|
376
|
+
const change = (value: string) => {
|
|
377
|
+
if (props.disabled) return;
|
|
378
|
+
valueRef.current = value;
|
|
379
|
+
onChange?.(value);
|
|
380
|
+
};
|
|
381
|
+
const next = (direction: 1 | -1) => step === undefined ? undefined : stepNumber(valueRef.current, direction, step, min ?? (allowNegative ? undefined : 0), max);
|
|
382
|
+
const advance = (direction: 1 | -1) => {
|
|
383
|
+
if (props.disabled) return;
|
|
384
|
+
const value = next(direction);
|
|
385
|
+
if (value === undefined) return;
|
|
386
|
+
// Restore after the controlled value commits, not before the DOM input updates.
|
|
387
|
+
pendingCursor.current = inputRef.current?.cursorOffset;
|
|
388
|
+
change(value);
|
|
389
|
+
};
|
|
390
|
+
const field = <TextField
|
|
391
|
+
{...props}
|
|
392
|
+
variant={stepping && well ? "plain" : props.variant}
|
|
393
|
+
backgroundColor={well ? colors.panel : props.backgroundColor}
|
|
394
|
+
label={stepping ? undefined : props.label}
|
|
395
|
+
accessibleLabel={props.accessibleLabel ?? props.label}
|
|
396
|
+
inputRef={inputRef}
|
|
397
|
+
width={stepping && props.width !== undefined ? Math.max(6, props.width - 8) : props.width}
|
|
398
|
+
onChange={(value) => change(sanitizeNumberInput(value, allowDecimal, allowNegative))}
|
|
399
|
+
onSubmit={(value) => { if (!props.disabled) onSubmit?.(sanitizeNumberInput(value, allowDecimal, allowNegative)); }}
|
|
400
|
+
onBlur={(value) => { if (!props.disabled) onBlur?.(sanitizeNumberInput(value, allowDecimal, allowNegative)); }}
|
|
401
|
+
onKeyDown={(event) => {
|
|
402
|
+
if (stepping && (event.name === "up" || event.name === "down")) {
|
|
403
|
+
event.preventDefault(); event.stopPropagation(); advance(event.name === "up" ? 1 : -1); return;
|
|
404
|
+
}
|
|
405
|
+
props.onKeyDown?.(event);
|
|
406
|
+
}}
|
|
407
|
+
/>;
|
|
408
|
+
if (!stepping) return field;
|
|
409
|
+
const name = props.accessibleLabel ?? props.label ?? "value";
|
|
410
|
+
return <Box flexDirection="column" width={props.width}>
|
|
411
|
+
{props.label ? <FieldLabel label={props.label} active={props.active} /> : null}
|
|
412
|
+
<Box flexDirection="row" alignItems="center" gap={1} backgroundColor={well ? colors.panel : undefined}
|
|
413
|
+
// An outline says the well takes typing even before it has focus.
|
|
414
|
+
style={native && well ? { borderRadius: "var(--gloom-radius-sm)", paddingLeft: 6, boxShadow: props.focused ? `inset 0 0 0 1px ${colors.borderFocused}, inset 0 -2px ${colors.borderFocused}` : `inset 0 0 0 1px ${colors.border}` } : undefined}
|
|
415
|
+
onMouseScroll={(event: { scroll?: { direction?: string }; preventDefault(): void; stopPropagation(): void }) => {
|
|
416
|
+
if (props.disabled || !["up", "down"].includes(event.scroll?.direction ?? "")) return;
|
|
417
|
+
event.preventDefault(); event.stopPropagation();
|
|
418
|
+
advance(event.scroll?.direction === "up" ? 1 : -1);
|
|
419
|
+
}}>
|
|
420
|
+
{field}
|
|
421
|
+
<Button label={`Decrease ${name}`} displayLabel="-" variant={well ? "plain" : undefined} width={3} compact disabled={props.disabled || next(-1) === undefined} onPress={() => { inputRef.current?.focus?.(); advance(-1); }} />
|
|
422
|
+
<Button label={`Increase ${name}`} displayLabel="+" variant={well ? "plain" : undefined} width={3} compact disabled={props.disabled || next(1) === undefined} onPress={() => { inputRef.current?.focus?.(); advance(1); }} />
|
|
423
|
+
</Box>
|
|
424
|
+
</Box>;
|
|
345
425
|
}
|
|
@@ -36,7 +36,9 @@ export type IconName =
|
|
|
36
36
|
| "ask"
|
|
37
37
|
| "bot"
|
|
38
38
|
| "team"
|
|
39
|
-
| "pro"
|
|
39
|
+
| "pro"
|
|
40
|
+
| "clock"
|
|
41
|
+
| "edit";
|
|
40
42
|
|
|
41
43
|
/** Terminal glyph for each icon; the desktop host draws SVG. */
|
|
42
44
|
export const ICON_GLYPHS: Record<IconName, string> = {
|
|
@@ -79,6 +81,9 @@ export const ICON_GLYPHS: Record<IconName, string> = {
|
|
|
79
81
|
bot: "⍠",
|
|
80
82
|
team: "◉",
|
|
81
83
|
pro: "◊",
|
|
84
|
+
// Time, for data that arrives late (a delayed quote).
|
|
85
|
+
clock: "◷",
|
|
86
|
+
edit: "✎",
|
|
82
87
|
};
|
|
83
88
|
|
|
84
89
|
export interface IconProps {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import { Box, Text, TextAttributes } from "../../ui";
|
|
3
|
+
import { useThemeColors } from "../../theme/theme-context";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Each line's height, in its own type: room for the font's ascent and descent,
|
|
7
|
+
* so a line's letters sit inside its box in every engine, without relying on
|
|
8
|
+
* `text-box` trimming (WebKit before 18.2 ignores it). Half of the space
|
|
9
|
+
* above and below the letters also separates the name from the line under it.
|
|
10
|
+
*/
|
|
11
|
+
const LINE_HEIGHT = 1.2;
|
|
12
|
+
|
|
13
|
+
/** Room above and below a line for accents and descenders that reach past it. */
|
|
14
|
+
const INK_ROOM = "0.3em";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The line clips for the ellipsis, and WebKit clips both ways whatever the
|
|
18
|
+
* other axis says, so the clip box reaches past the line by padding, and an
|
|
19
|
+
* equal negative margin keeps the line where it was.
|
|
20
|
+
*/
|
|
21
|
+
const LINE_STYLE = {
|
|
22
|
+
display: "block",
|
|
23
|
+
minWidth: 0,
|
|
24
|
+
maxWidth: "100%",
|
|
25
|
+
lineHeight: LINE_HEIGHT,
|
|
26
|
+
overflow: "hidden",
|
|
27
|
+
paddingBlock: INK_ROOM,
|
|
28
|
+
marginBlock: `calc(-1 * ${INK_ROOM})`,
|
|
29
|
+
textOverflow: "ellipsis",
|
|
30
|
+
whiteSpace: "nowrap",
|
|
31
|
+
} as CSSProperties;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The art beside the two lines (a `LogoMark` of size `lead`) is a square
|
|
35
|
+
* exactly as tall as the two lines, in their type, so the name and the line
|
|
36
|
+
* under it always sit within its height, the block centred on it.
|
|
37
|
+
*/
|
|
38
|
+
const LEADING_STYLE: CSSProperties = {
|
|
39
|
+
flexShrink: 0,
|
|
40
|
+
fontSize: "var(--gloom-type-ui)",
|
|
41
|
+
width: `${2 * LINE_HEIGHT}em`,
|
|
42
|
+
height: `${2 * LINE_HEIGHT}em`,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export interface ListRowLinesProps {
|
|
46
|
+
label: string;
|
|
47
|
+
/** The muted line under the label, such as how a broker connects. */
|
|
48
|
+
description?: string;
|
|
49
|
+
/** Art before the lines, such as a broker's logo. Decoration: hidden from assistive technology. */
|
|
50
|
+
leading?: ReactNode;
|
|
51
|
+
/** The label's colour; the theme's text colour by default. */
|
|
52
|
+
labelColor?: string;
|
|
53
|
+
/** The label in bold, as a selected row's. */
|
|
54
|
+
labelBold?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A desktop and web list row's name with one muted line under it, and its
|
|
59
|
+
* logo before them, the block centred in the row. Every two-line row with
|
|
60
|
+
* art uses it (`ListView`'s `rowDescriptions`, the first-run guide's list),
|
|
61
|
+
* so the lines sit on the logo the same way everywhere.
|
|
62
|
+
*/
|
|
63
|
+
export function ListRowLines({ label, description, leading, labelColor, labelBold = false }: ListRowLinesProps) {
|
|
64
|
+
const colors = useThemeColors();
|
|
65
|
+
return (
|
|
66
|
+
<Box
|
|
67
|
+
flexDirection="row"
|
|
68
|
+
alignItems="center"
|
|
69
|
+
minWidth={0}
|
|
70
|
+
flexShrink={1}
|
|
71
|
+
data-gloom-role="list-row-lines"
|
|
72
|
+
style={{ gap: "var(--gloom-space-sm)", fontSize: "var(--gloom-type-ui)" }}
|
|
73
|
+
>
|
|
74
|
+
{leading !== undefined && (
|
|
75
|
+
<Box aria-hidden="true" data-gloom-role="list-row-leading" style={LEADING_STYLE}>
|
|
76
|
+
{leading}
|
|
77
|
+
</Box>
|
|
78
|
+
)}
|
|
79
|
+
<Box flexDirection="column" minWidth={0} flexShrink={1}>
|
|
80
|
+
<Text fg={labelColor ?? colors.text} attributes={labelBold ? TextAttributes.BOLD : 0} style={LINE_STYLE}>
|
|
81
|
+
{label}
|
|
82
|
+
</Text>
|
|
83
|
+
{description ? <Text fg={colors.textMuted} style={LINE_STYLE}>{description}</Text> : null}
|
|
84
|
+
</Box>
|
|
85
|
+
</Box>
|
|
86
|
+
);
|
|
87
|
+
}
|