gloomberb 0.18.1 → 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/types.ts +5 -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/market.ts +281 -154
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +100 -50
- 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 +27 -3
- 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/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +11 -2
- 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 +58 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -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/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 +5 -5
- 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/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/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/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 +35 -0
- 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/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-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- 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/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- 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/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-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/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 +5 -28
- 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/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 +62 -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/tickers/listing-fate.ts +27 -8
- 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/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 +32 -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
|
@@ -37,6 +37,7 @@ export function WebButton({
|
|
|
37
37
|
expanded,
|
|
38
38
|
onPress,
|
|
39
39
|
variant = "secondary",
|
|
40
|
+
tone,
|
|
40
41
|
disabled = false,
|
|
41
42
|
pending = false,
|
|
42
43
|
active = false,
|
|
@@ -49,7 +50,7 @@ export function WebButton({
|
|
|
49
50
|
title,
|
|
50
51
|
}: ButtonProps) {
|
|
51
52
|
const colors = useThemeColors();
|
|
52
|
-
const palette = buttonPalette({ variant, active, disabled }, colors);
|
|
53
|
+
const palette = buttonPalette({ variant, active, disabled, tone }, colors);
|
|
53
54
|
const filled = active || variant === "primary" || variant === "danger";
|
|
54
55
|
|
|
55
56
|
return (
|
|
@@ -261,10 +262,12 @@ const COMFORTABLE_FIELD_HEIGHT = 26;
|
|
|
261
262
|
|
|
262
263
|
export function WebTextField({
|
|
263
264
|
label,
|
|
265
|
+
accessibleLabel,
|
|
264
266
|
value,
|
|
265
267
|
placeholder,
|
|
266
268
|
maxLength,
|
|
267
269
|
focused,
|
|
270
|
+
disabled = false,
|
|
268
271
|
width,
|
|
269
272
|
inputRef,
|
|
270
273
|
onChange,
|
|
@@ -290,6 +293,7 @@ export function WebTextField({
|
|
|
290
293
|
const renderer = useRendererHost();
|
|
291
294
|
const { nativeContextMenu } = useUiCapabilities();
|
|
292
295
|
const plain = variant === "plain";
|
|
296
|
+
const well = variant === "well";
|
|
293
297
|
const comfortable = size === "comfortable";
|
|
294
298
|
|
|
295
299
|
return (
|
|
@@ -311,6 +315,7 @@ export function WebTextField({
|
|
|
311
315
|
alignItems="center"
|
|
312
316
|
backgroundColor={plain ? "transparent" : resolvedBackgroundColor}
|
|
313
317
|
onMouseDown={() => {
|
|
318
|
+
if (disabled) return;
|
|
314
319
|
onMouseDown?.();
|
|
315
320
|
resolvedInputRef.current?.focus?.();
|
|
316
321
|
}}
|
|
@@ -324,12 +329,16 @@ export function WebTextField({
|
|
|
324
329
|
data-gloom-role="desktop-text-field"
|
|
325
330
|
style={{
|
|
326
331
|
height: comfortable ? COMFORTABLE_FIELD_HEIGHT : undefined,
|
|
327
|
-
border: plain ? "none" : `1px solid ${controlBorderColor(focused, false, colors)}`,
|
|
328
|
-
borderRadius: plain ? 0 : CONTROL_RADIUS,
|
|
332
|
+
border: plain || well ? "none" : `1px solid ${controlBorderColor(focused, false, colors)}`,
|
|
333
|
+
borderRadius: plain ? 0 : well ? "var(--gloom-radius-sm)" : CONTROL_RADIUS,
|
|
334
|
+
// A well has no border: focus is the accent along its foot.
|
|
335
|
+
boxShadow: well && focused ? "inset 0 -2px var(--gloom-accent)" : undefined,
|
|
329
336
|
overflow: "hidden",
|
|
330
337
|
}}
|
|
331
338
|
>
|
|
332
339
|
<Input
|
|
340
|
+
aria-label={accessibleLabel ?? label}
|
|
341
|
+
disabled={disabled}
|
|
333
342
|
ref={resolvedInputRef as RefObject<InputRenderable | null>}
|
|
334
343
|
width="100%"
|
|
335
344
|
value={value}
|
|
@@ -459,15 +468,18 @@ export function WebMessageComposer({
|
|
|
459
468
|
}
|
|
460
469
|
|
|
461
470
|
export function WebSegmentedControl({
|
|
471
|
+
accessibleLabel,
|
|
462
472
|
options,
|
|
463
473
|
value,
|
|
464
474
|
onChange,
|
|
465
475
|
focused,
|
|
466
476
|
width,
|
|
467
477
|
wrap = false,
|
|
478
|
+
size = "default",
|
|
468
479
|
}: SegmentedControlProps) {
|
|
469
480
|
const colors = useThemeColors();
|
|
470
481
|
const enabled = options.filter((option) => !option.disabled);
|
|
482
|
+
const large = size === "large";
|
|
471
483
|
const selectAdjacent = (direction: -1 | 1) => {
|
|
472
484
|
const index = enabled.findIndex((option) => option.value === value);
|
|
473
485
|
const next = enabled[index < 0 ? 0 : (index + direction + enabled.length) % enabled.length];
|
|
@@ -476,26 +488,37 @@ export function WebSegmentedControl({
|
|
|
476
488
|
|
|
477
489
|
// A segmented choice is a tab strip (the tab rule in styles.css): text, the
|
|
478
490
|
// chosen one bright and bold on the accent underline. One row tall, so it
|
|
479
|
-
// never bleeds over the pane title or the table header around it.
|
|
491
|
+
// never bleeds over the pane title or the table header around it. A large
|
|
492
|
+
// one, such as an order's side, is two rows of blocks that share the width,
|
|
493
|
+
// the chosen one filled in its tone.
|
|
480
494
|
return (
|
|
481
495
|
<Box
|
|
482
496
|
flexDirection="row"
|
|
483
497
|
flexWrap={wrap ? "wrap" : "nowrap"}
|
|
484
498
|
width={width}
|
|
485
|
-
height={wrap ? undefined : 1}
|
|
499
|
+
height={wrap ? undefined : large ? 2 : 1}
|
|
486
500
|
alignItems="stretch"
|
|
487
501
|
role="radiogroup"
|
|
502
|
+
aria-label={accessibleLabel}
|
|
488
503
|
data-gloom-role="segmented-control"
|
|
504
|
+
data-size={large ? "large" : undefined}
|
|
489
505
|
data-focused={focused ? "true" : undefined}
|
|
506
|
+
style={large ? {
|
|
507
|
+
borderRadius: "var(--gloom-radius-md)",
|
|
508
|
+
overflow: "hidden",
|
|
509
|
+
boxShadow: focused ? "0 0 0 2px var(--gloom-accent)" : undefined,
|
|
510
|
+
} : undefined}
|
|
490
511
|
>
|
|
491
512
|
{options.map((option) => {
|
|
492
513
|
const active = option.value === value;
|
|
514
|
+
const tone = option.tone ? colors[option.tone] : undefined;
|
|
493
515
|
return (
|
|
494
516
|
<Box
|
|
495
517
|
key={option.value}
|
|
496
518
|
flexDirection="row"
|
|
497
519
|
alignItems="center"
|
|
498
520
|
justifyContent="center"
|
|
521
|
+
flexGrow={large ? 1 : undefined}
|
|
499
522
|
onMouseDown={() => {
|
|
500
523
|
if (!option.disabled) onChange?.(option.value);
|
|
501
524
|
}}
|
|
@@ -503,7 +526,7 @@ export function WebSegmentedControl({
|
|
|
503
526
|
// after the keyboard has moved on to another pane.
|
|
504
527
|
onClick={(event: { currentTarget: unknown; detail: number }) => releasePointerFocus(event.currentTarget, event.detail)}
|
|
505
528
|
data-gloom-interactive={option.disabled ? undefined : "true"}
|
|
506
|
-
data-gloom-role="tab-button"
|
|
529
|
+
data-gloom-role={large ? "segment-block" : "tab-button"}
|
|
507
530
|
data-active={active ? "true" : undefined}
|
|
508
531
|
role="radio"
|
|
509
532
|
aria-checked={active}
|
|
@@ -521,9 +544,18 @@ export function WebSegmentedControl({
|
|
|
521
544
|
onChange?.(option.value);
|
|
522
545
|
}
|
|
523
546
|
}}
|
|
524
|
-
style={{
|
|
547
|
+
style={large ? {
|
|
548
|
+
height: "100%",
|
|
549
|
+
cursor: option.disabled ? "default" : "pointer",
|
|
550
|
+
outline: "none",
|
|
551
|
+
fontSize: "var(--gloom-type-heading)",
|
|
552
|
+
fontWeight: "var(--gloom-weight-bold)",
|
|
553
|
+
background: option.disabled ? "var(--gloom-panel)" : active ? tone ?? colors.selected : "var(--gloom-panel)",
|
|
554
|
+
color: option.disabled ? colors.textMuted : active ? tone ? colors.bg : colors.selectedText : tone ?? colors.textDim,
|
|
555
|
+
} : {
|
|
525
556
|
height: wrap ? undefined : "100%",
|
|
526
557
|
cursor: option.disabled ? "default" : "pointer",
|
|
558
|
+
...(tone ? { "--tab-fg": tone, "--tab-active-fg": tone } as CSSProperties : {}),
|
|
527
559
|
...(option.disabled ? { color: colors.textMuted } : {}),
|
|
528
560
|
}}
|
|
529
561
|
>
|
|
@@ -151,6 +151,20 @@ const ICONS: Record<IconName, ReactNode> = {
|
|
|
151
151
|
<path d="M1.5 5.3h9M4.8 2.5 4.3 5.3 6 10.1l1.7-4.8-.5-2.8" stroke="currentColor" strokeWidth=".9" strokeLinejoin="round" />
|
|
152
152
|
</>
|
|
153
153
|
),
|
|
154
|
+
// A clock face at ten past: data that arrives late.
|
|
155
|
+
clock: (
|
|
156
|
+
<>
|
|
157
|
+
<circle cx="6" cy="6" r="4.4" stroke="currentColor" strokeWidth="1.2" />
|
|
158
|
+
<path d="M6 3.4V6l1.9 1.2" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
|
|
159
|
+
</>
|
|
160
|
+
),
|
|
161
|
+
// A pencil, for changing something in place.
|
|
162
|
+
edit: (
|
|
163
|
+
<>
|
|
164
|
+
<path d="M2.2 9.8l.5-2.3 5.4-5.4a1.1 1.1 0 0 1 1.6 0l.2.2a1.1 1.1 0 0 1 0 1.6L4.5 9.3l-2.3.5Z" stroke="currentColor" strokeWidth="1.1" strokeLinejoin="round" />
|
|
165
|
+
<path d="M7.3 2.9l1.8 1.8" stroke="currentColor" strokeWidth="1.1" />
|
|
166
|
+
</>
|
|
167
|
+
),
|
|
154
168
|
restore: (
|
|
155
169
|
<>
|
|
156
170
|
<rect x="2" y="4" width="6" height="6" rx="0.8" stroke="currentColor" strokeWidth="1.1" />
|
|
@@ -3,6 +3,7 @@ import { useEffect, useId, useRef, useState, type CSSProperties } from "react";
|
|
|
3
3
|
import { Box, ScrollBox, Text, type ScrollBoxRenderable } from "../../../ui";
|
|
4
4
|
import { TextAttributes } from "../../../ui";
|
|
5
5
|
import type { ListRowState, ListViewItem, ListViewProps } from "../../../components/ui/list-view";
|
|
6
|
+
import { ListRowLines } from "../../../components/ui/list-row-lines";
|
|
6
7
|
import { hoverBg, type ThemeColors } from "../../../theme/colors";
|
|
7
8
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
8
9
|
import { WEB_CELL_HEIGHT } from "../../../theme/font-scale";
|
|
@@ -18,9 +19,12 @@ import {
|
|
|
18
19
|
function DefaultDesktopRow({
|
|
19
20
|
item,
|
|
20
21
|
selected,
|
|
22
|
+
stacked,
|
|
21
23
|
}: {
|
|
22
24
|
item: ListViewItem;
|
|
23
25
|
selected: boolean;
|
|
26
|
+
/** The description on a second line under the label (`rowDescriptions`). */
|
|
27
|
+
stacked: boolean;
|
|
24
28
|
}) {
|
|
25
29
|
const colors = useThemeColors();
|
|
26
30
|
return (
|
|
@@ -28,28 +32,52 @@ function DefaultDesktopRow({
|
|
|
28
32
|
flexDirection="row"
|
|
29
33
|
justifyContent="space-between"
|
|
30
34
|
width="100%"
|
|
35
|
+
// A two-line row is taller than its lines: they sit in its middle.
|
|
36
|
+
height={stacked ? "100%" : undefined}
|
|
31
37
|
minWidth={0}
|
|
32
38
|
alignItems="center"
|
|
33
39
|
style={{ boxSizing: "border-box" }}
|
|
34
40
|
>
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
41
|
+
{stacked ? (
|
|
42
|
+
<ListRowLines
|
|
43
|
+
label={item.label}
|
|
44
|
+
description={item.description}
|
|
45
|
+
leading={item.leading}
|
|
46
|
+
labelColor={item.color}
|
|
47
|
+
labelBold={selected}
|
|
48
|
+
/>
|
|
49
|
+
) : (
|
|
50
|
+
<Box
|
|
51
|
+
flexDirection="row"
|
|
52
|
+
alignItems="center"
|
|
53
|
+
minWidth={0}
|
|
54
|
+
flexShrink={1}
|
|
55
|
+
overflow="hidden"
|
|
56
|
+
style={item.leading !== undefined ? { gap: "var(--gloom-space-sm)" } : undefined}
|
|
49
57
|
>
|
|
50
|
-
{item.
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
{item.leading !== undefined && (
|
|
59
|
+
<Box flexShrink={0} alignItems="center" aria-hidden="true" data-gloom-role="list-row-leading">
|
|
60
|
+
{item.leading}
|
|
61
|
+
</Box>
|
|
62
|
+
)}
|
|
63
|
+
<Text
|
|
64
|
+
fg={item.color ?? (selected ? colors.text : colors.textDim)}
|
|
65
|
+
attributes={selected ? TextAttributes.BOLD : 0}
|
|
66
|
+
style={{
|
|
67
|
+
// The base text style pins flexShrink to 0, which lets a long
|
|
68
|
+
// label run under the detail instead of shrinking to an ellipsis.
|
|
69
|
+
flexShrink: 1,
|
|
70
|
+
minWidth: 0,
|
|
71
|
+
maxWidth: "100%",
|
|
72
|
+
overflow: "hidden",
|
|
73
|
+
textOverflow: "ellipsis",
|
|
74
|
+
whiteSpace: "nowrap",
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
{item.label}
|
|
78
|
+
</Text>
|
|
79
|
+
</Box>
|
|
80
|
+
)}
|
|
53
81
|
{item.detail && (
|
|
54
82
|
<Text fg={colors.textMuted} style={{ flexShrink: 0, marginLeft: 12 }}>
|
|
55
83
|
{item.detail}
|
|
@@ -59,6 +87,9 @@ function DefaultDesktopRow({
|
|
|
59
87
|
);
|
|
60
88
|
}
|
|
61
89
|
|
|
90
|
+
/** A row with its description under its label: the two lines and a quarter row above and below them. */
|
|
91
|
+
const STACKED_ROW_HEIGHT = 2.5;
|
|
92
|
+
|
|
62
93
|
const FRAMED_LIST_PADDING_PX = 4;
|
|
63
94
|
/** What a framed list adds above and below its rows: padding and a 1px border on each side. */
|
|
64
95
|
const FRAMED_LIST_CHROME_PX = 2 * (FRAMED_LIST_PADDING_PX + 1);
|
|
@@ -84,12 +115,13 @@ export function WebListView({
|
|
|
84
115
|
renderRow,
|
|
85
116
|
getRowBackgroundColor,
|
|
86
117
|
showSelectedDescription = false,
|
|
118
|
+
rowDescriptions = false,
|
|
87
119
|
emptyMessage = "Nothing to show.",
|
|
88
120
|
bgColor,
|
|
89
121
|
selectedBgColor,
|
|
90
122
|
hoverBgColor,
|
|
91
123
|
rowGap = 1,
|
|
92
|
-
rowHeight = 1,
|
|
124
|
+
rowHeight = rowDescriptions ? STACKED_ROW_HEIGHT : 1,
|
|
93
125
|
surface,
|
|
94
126
|
selection = "frame",
|
|
95
127
|
height,
|
|
@@ -242,7 +274,7 @@ export function WebListView({
|
|
|
242
274
|
>
|
|
243
275
|
{renderRow
|
|
244
276
|
? renderRow(item, state, index)
|
|
245
|
-
: <DefaultDesktopRow item={item} selected={selected} />}
|
|
277
|
+
: <DefaultDesktopRow item={item} selected={selected} stacked={rowDescriptions} />}
|
|
246
278
|
</Box>
|
|
247
279
|
);
|
|
248
280
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @jsxImportSource react */
|
|
2
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
|
|
3
3
|
import type { HostMenuItem, HostMenuProps } from "../../../ui/host";
|
|
4
4
|
import { CheckboxBox } from "./controls";
|
|
5
5
|
import { WebIcon } from "./icons";
|
|
@@ -10,9 +10,12 @@ function selectable(item: HostMenuItem): boolean {
|
|
|
10
10
|
return item.kind !== "divider" && item.kind !== "heading" && !item.disabled;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
const LEADING_STYLE: CSSProperties = { display: "inline-flex", flex: "none", alignItems: "center" };
|
|
14
|
+
|
|
13
15
|
/** Desktop Menu: the one list every popover menu in the app renders. */
|
|
14
16
|
export function WebMenu({ items, onSelect, selection = "none", title, label, onClose, highlightedId, onHighlight }: HostMenuProps) {
|
|
15
17
|
const enabled = useMemo(() => items.filter(selectable), [items]);
|
|
18
|
+
const alignLeading = items.find((item) => item.leading !== undefined)?.leading;
|
|
16
19
|
const controlled = highlightedId !== undefined;
|
|
17
20
|
const [ownHighlighted, setOwnHighlighted] = useState<string | null>(
|
|
18
21
|
() => items.find((item) => item.selected && selectable(item))?.id ?? enabled[0]?.id ?? null,
|
|
@@ -92,6 +95,12 @@ export function WebMenu({ items, onSelect, selection = "none", title, label, onC
|
|
|
92
95
|
{(selection === "multi" || toggle) && (
|
|
93
96
|
<CheckboxBox checked={!!item.checked} />
|
|
94
97
|
)}
|
|
98
|
+
{item.leading !== undefined ? (
|
|
99
|
+
<span aria-hidden="true" style={LEADING_STYLE}>{item.leading}</span>
|
|
100
|
+
) : alignLeading !== undefined ? (
|
|
101
|
+
// The room another item's artwork takes, so every label starts in one column.
|
|
102
|
+
<span aria-hidden="true" style={{ ...LEADING_STYLE, visibility: "hidden" }}>{alignLeading}</span>
|
|
103
|
+
) : null}
|
|
95
104
|
<span className="gloom-menu-label">
|
|
96
105
|
<span className="gloom-menu-text" style={item.color ? { color: item.color } : undefined}>{item.label}</span>
|
|
97
106
|
{item.description ? <span className="gloom-menu-description">{item.description}</span> : null}
|
|
@@ -38,6 +38,7 @@ export function WebSelectField({
|
|
|
38
38
|
const [open, setOpen] = useState(false);
|
|
39
39
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
40
40
|
const inline = variant === "inline";
|
|
41
|
+
const well = variant === "well";
|
|
41
42
|
const current = options.find((option) => option.value === value);
|
|
42
43
|
const showUnset = includeUnsetOption && !current;
|
|
43
44
|
|
|
@@ -54,9 +55,9 @@ export function WebSelectField({
|
|
|
54
55
|
setSelectRef.current = selectRef;
|
|
55
56
|
|
|
56
57
|
const resolvedWidth = width ?? (inline ? "auto" : 184);
|
|
57
|
-
// A field is one row inside its border, as a text field is; inline it is the row.
|
|
58
|
+
// A field is one row inside its border, as a text field is; inline it is the row, and a well has no border.
|
|
58
59
|
const row = WEB_CELL_HEIGHT;
|
|
59
|
-
const resolvedHeight = height ?? (inline ? row : row + 2);
|
|
60
|
+
const resolvedHeight = height ?? (inline || well ? row : row + 2);
|
|
60
61
|
const style: CSSProperties = {
|
|
61
62
|
width: resolvedWidth,
|
|
62
63
|
height: resolvedHeight,
|
|
@@ -65,10 +66,11 @@ export function WebSelectField({
|
|
|
65
66
|
justifyContent: "space-between",
|
|
66
67
|
gap: "var(--gloom-space-xs)",
|
|
67
68
|
color: disabled ? colors.textMuted : colors.text,
|
|
68
|
-
// The field look: sunk into the surface, the control border, the accent while open.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
// The field look: sunk into the surface, the control border, the accent while open. A well is the panel's
|
|
70
|
+
// fill with no border, for a dense form such as the order ticket.
|
|
71
|
+
backgroundColor: inline ? "transparent" : well ? "var(--gloom-panel)" : "var(--gloom-surface-sunken)",
|
|
72
|
+
border: inline || well ? "none" : `1px solid ${open ? "var(--gloom-accent)" : "var(--gloom-control-border)"}`,
|
|
73
|
+
borderRadius: inline ? 0 : well ? "var(--gloom-radius-sm)" : "var(--gloom-radius-md)",
|
|
72
74
|
padding: inline ? 0 : "0 var(--gloom-space-sm)",
|
|
73
75
|
cursor: disabled ? "default" : "pointer",
|
|
74
76
|
font: "inherit",
|
|
@@ -18,6 +18,7 @@ function cellInset(value: unknown, axis: "x" | "y"): CSSProperties["paddingLeft"
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function commonStyle(props: Record<string, unknown>): CSSProperties {
|
|
21
|
+
const border = props.border;
|
|
21
22
|
const hasFixedInlineSize = props.width != null || props.flexBasis != null;
|
|
22
23
|
const hasFixedBlockSize = props.height != null;
|
|
23
24
|
const gapUnit = props.flexDirection === "row" ? WEB_CELL_WIDTH : WEB_CELL_HEIGHT;
|
|
@@ -60,7 +61,11 @@ export function commonStyle(props: Record<string, unknown>): CSSProperties {
|
|
|
60
61
|
...(props.columnGap != null ? { columnGap: cellWidth(props.columnGap) as CSSProperties["columnGap"] } : {}),
|
|
61
62
|
...(props.rowGap != null ? { rowGap: cellHeight(props.rowGap) as CSSProperties["rowGap"] } : {}),
|
|
62
63
|
overflow: props.overflow as CSSProperties["overflow"],
|
|
63
|
-
|
|
64
|
+
// A requested divider or accent is not a frame. Use longhands consistently
|
|
65
|
+
// so switching between one edge, all edges and no border clears stale sides.
|
|
66
|
+
borderStyle: props.border ? "solid" : undefined,
|
|
67
|
+
borderColor: props.border ? typeof props.borderColor === "string" ? props.borderColor : "var(--gloom-border)" : undefined,
|
|
68
|
+
borderWidth: Array.isArray(border) ? ["top", "right", "bottom", "left"].map(side => border.includes(side) ? "1px" : "0px").join(" ") : border ? "1px" : undefined,
|
|
64
69
|
boxSizing: "border-box",
|
|
65
70
|
minInlineSize: zeroMinInlineSize ? 0 : undefined,
|
|
66
71
|
minBlockSize: zeroMinBlockSize ? 0 : undefined,
|
|
@@ -258,6 +258,11 @@ export function WebTabs({
|
|
|
258
258
|
tab.onContextMenu?.(tab.value, event);
|
|
259
259
|
} : undefined}
|
|
260
260
|
>
|
|
261
|
+
{tab.leading !== undefined && (
|
|
262
|
+
<span data-gloom-role="tab-leading" aria-hidden="true" style={{ display: "flex", flex: "none", alignItems: "center" }}>
|
|
263
|
+
{tab.leading}
|
|
264
|
+
</span>
|
|
265
|
+
)}
|
|
261
266
|
<span
|
|
262
267
|
data-gloom-role="tab-label"
|
|
263
268
|
style={{
|
|
@@ -349,6 +354,7 @@ export function WebTabs({
|
|
|
349
354
|
items={tabs.map((tab) => ({
|
|
350
355
|
id: tab.value,
|
|
351
356
|
label: tab.label,
|
|
357
|
+
leading: tab.leading,
|
|
352
358
|
disabled: tab.disabled,
|
|
353
359
|
selected: tab.value === activeValue,
|
|
354
360
|
}))}
|
|
@@ -70,6 +70,10 @@ button, [data-gloom-interactive="true"] { cursor: pointer; }
|
|
|
70
70
|
[data-gloom-role="desktop-button"]:is([data-gloom-variant="plain"], [data-gloom-variant="ghost"], [data-gloom-variant="secondary"]):not(:disabled):not([aria-pressed="true"]):hover {
|
|
71
71
|
background-color: var(--gloom-hover-fill);
|
|
72
72
|
}
|
|
73
|
+
/* Quiet quick actions take the same wash, over a chosen one's fill too. */
|
|
74
|
+
[data-gloom-role="desktop-button"][data-gloom-variant="quiet"]:not(:disabled):hover {
|
|
75
|
+
background-color: var(--gloom-hover-fill) !important;
|
|
76
|
+
}
|
|
73
77
|
[data-gloom-hover-bg="true"]:hover {
|
|
74
78
|
background-color: var(--gloom-box-hover-bg) !important;
|
|
75
79
|
}
|
|
@@ -941,12 +945,14 @@ html[data-gloom-screenshot="true"] [data-gloom-role="chart-watermark"] {
|
|
|
941
945
|
}
|
|
942
946
|
.gloom-qb-inline .gloom-qb-view { margin-left: 0; box-shadow: none; align-self: stretch; }
|
|
943
947
|
/* A long inline choice (an expiry strip) scrolls by itself, as the terminal's
|
|
944
|
-
does, so its label and the controls beside it stay in view.
|
|
948
|
+
does, so its label and the controls beside it stay in view. It narrows with
|
|
949
|
+
its group and has no floor of its own: a floor wider than the room the bar
|
|
950
|
+
gives it drew its next choice under the control beside it. */
|
|
945
951
|
.gloom-qb-filters[data-strip="true"],
|
|
946
952
|
.gloom-qb-inline[data-strip="true"] { flex: 0 1 auto; min-width: 0; }
|
|
947
953
|
.gloom-qb-inline[data-strip="true"] .gloom-qb-segments {
|
|
948
954
|
flex: 0 1 auto;
|
|
949
|
-
min-width:
|
|
955
|
+
min-width: 0;
|
|
950
956
|
overflow-x: auto;
|
|
951
957
|
scrollbar-width: none;
|
|
952
958
|
}
|
|
@@ -1354,8 +1360,9 @@ html[data-gloom-screenshot="true"] [data-gloom-role="chart-watermark"] {
|
|
|
1354
1360
|
white-space: nowrap;
|
|
1355
1361
|
}
|
|
1356
1362
|
|
|
1357
|
-
/* Icon-only button: pane header, footer, dialog close, window controls.
|
|
1358
|
-
|
|
1363
|
+
/* Icon-only button: pane header, footer, dialog close, window controls. A
|
|
1364
|
+
footer status icon (a delayed source's clock) takes the same box. */
|
|
1365
|
+
.gloom-icon-button, [data-gloom-role="pane-footer-icon"] {
|
|
1359
1366
|
flex: none;
|
|
1360
1367
|
display: inline-flex;
|
|
1361
1368
|
align-items: center;
|
|
@@ -1372,7 +1379,7 @@ html[data-gloom-screenshot="true"] [data-gloom-role="chart-watermark"] {
|
|
|
1372
1379
|
cursor: pointer;
|
|
1373
1380
|
transition: background-color 120ms ease, color 120ms ease;
|
|
1374
1381
|
}
|
|
1375
|
-
.gloom-icon-button:disabled { cursor: default; }
|
|
1382
|
+
.gloom-icon-button:disabled, [data-gloom-role="pane-footer-icon"] { cursor: default; }
|
|
1376
1383
|
.gloom-icon-button:not(:disabled):hover { background: var(--gloom-hover-fill); color: var(--gloom-text-bright); }
|
|
1377
1384
|
.gloom-icon-button[aria-pressed="true"] { color: var(--gloom-warning); }
|
|
1378
1385
|
.gloom-qb-view button:disabled { color: var(--gloom-text-muted); opacity: 0.55; cursor: default; }
|
|
@@ -30,6 +30,7 @@ const BROKER_INVOKE_OPERATIONS = new Set([
|
|
|
30
30
|
"getChartResolutionCapabilities",
|
|
31
31
|
"getOptionsChain",
|
|
32
32
|
"listOpenOrders",
|
|
33
|
+
"getOrderStatus",
|
|
33
34
|
"listExecutions",
|
|
34
35
|
"previewOrder",
|
|
35
36
|
"placeOrder",
|
|
@@ -116,6 +117,8 @@ function invokeBrokerOperation(
|
|
|
116
117
|
case "previewOrder":
|
|
117
118
|
case "placeOrder":
|
|
118
119
|
return (broker[operation] as any)?.call(broker, instance, args[0]);
|
|
120
|
+
case "getOrderStatus":
|
|
121
|
+
return broker.getOrderStatus?.(instance, args[0] as number);
|
|
119
122
|
case "modifyOrder":
|
|
120
123
|
return broker.modifyOrder?.(instance, args[0] as number, args[1] as never);
|
|
121
124
|
case "cancelOrder":
|
|
@@ -167,6 +170,15 @@ function createBrokerCapability(options: CoreCapabilityOptions): PluginCapabilit
|
|
|
167
170
|
},
|
|
168
171
|
);
|
|
169
172
|
}),
|
|
173
|
+
orders: stream((input: any, emit) => {
|
|
174
|
+
const instanceId = requireString(input.instanceId, "Broker instance");
|
|
175
|
+
const { instance, broker } = resolveBrokerInstance(options, instanceId);
|
|
176
|
+
if (!broker.subscribeOrders) throw new Error(`Broker "${broker.name}" does not stream orders.`);
|
|
177
|
+
return broker.subscribeOrders(instance, {
|
|
178
|
+
order: (order) => emit({ kind: "order", order }),
|
|
179
|
+
live: (live) => emit({ kind: "orders-live", live }),
|
|
180
|
+
});
|
|
181
|
+
}),
|
|
170
182
|
removeInstance: op(async (input: any) => {
|
|
171
183
|
const { instance, broker } = resolveBrokerInstance(options, input.instanceId);
|
|
172
184
|
await broker.disconnect?.(instance);
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
loadExternalPlugin,
|
|
15
15
|
} from "../../../plugins/loader";
|
|
16
16
|
import { createNodePluginManager } from "../../../plugins/manager-node";
|
|
17
|
+
import { readPluginIcon } from "../../../plugins/plugin-icon";
|
|
17
18
|
import type { PluginRegistry } from "../../../plugins/registry";
|
|
18
19
|
import { desktopRendererCapabilityManifests } from "./desktop/initialization";
|
|
19
20
|
import { reportCrash } from "../../../telemetry/crash-reports";
|
|
@@ -70,6 +71,7 @@ async function bundlePluginDirectory(
|
|
|
70
71
|
|
|
71
72
|
const { plugin } = loaded;
|
|
72
73
|
const directory = basename(pluginDir);
|
|
74
|
+
const icon = readPluginIcon(pluginDir);
|
|
73
75
|
const base = {
|
|
74
76
|
id: plugin.id,
|
|
75
77
|
name: plugin.name,
|
|
@@ -80,6 +82,7 @@ async function bundlePluginDirectory(
|
|
|
80
82
|
...(loaded.commit ? { commit: loaded.commit } : {}),
|
|
81
83
|
...(loaded.repo ? { repo: loaded.repo } : {}),
|
|
82
84
|
...(loaded.linked ? { linked: true } : {}),
|
|
85
|
+
...(icon ? { icon } : {}),
|
|
83
86
|
...(plugin.targets ? { targets: plugin.targets } : {}),
|
|
84
87
|
};
|
|
85
88
|
|
|
@@ -130,6 +130,8 @@ export interface DesktopExternalPluginBundle {
|
|
|
130
130
|
/** `owner/repo` of the checkout's GitHub origin. */
|
|
131
131
|
repo?: string;
|
|
132
132
|
linked?: boolean;
|
|
133
|
+
/** The plugin's logo as a `data:` URL, read from the checkout by the Bun process. */
|
|
134
|
+
icon?: string;
|
|
133
135
|
/** ES module source, absent when `error` or `unsupportedTarget` is set. */
|
|
134
136
|
code?: string;
|
|
135
137
|
targets?: readonly ("cli" | "tui" | "desktop" | "web")[];
|
|
@@ -125,6 +125,22 @@ const client: BrokerRemoteClient = {
|
|
|
125
125
|
});
|
|
126
126
|
},
|
|
127
127
|
|
|
128
|
+
subscribeOrders(instanceId, listener) {
|
|
129
|
+
return subscribeBackendCapability({
|
|
130
|
+
subscriptionId: `broker-orders:${instanceId}:${nextSubscriptionId++}`,
|
|
131
|
+
capabilityId: BROKER_CAPABILITY_ID,
|
|
132
|
+
operationId: "orders",
|
|
133
|
+
payload: { instanceId },
|
|
134
|
+
onEvent: (message) => {
|
|
135
|
+
const event = message as BrokerRemoteEvent;
|
|
136
|
+
if (event.kind === "order") listener.order(event.order);
|
|
137
|
+
else if (event.kind === "orders-live") listener.live?.(event.live);
|
|
138
|
+
},
|
|
139
|
+
// A stream that cannot open leaves the host polling open orders.
|
|
140
|
+
onError: (error) => { listener.live?.(false); console.error("Failed to subscribe to broker orders", error); },
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
|
|
128
144
|
async removeInstance(instanceId) {
|
|
129
145
|
await invokeBrokerCapability("removeInstance", { instanceId });
|
|
130
146
|
statuses.delete(instanceId);
|
|
@@ -28,6 +28,7 @@ export async function loadDesktopExternalPlugin(bundle: DesktopExternalPluginBun
|
|
|
28
28
|
...(bundle.commit ? { commit: bundle.commit } : {}),
|
|
29
29
|
...(bundle.repo ? { repo: bundle.repo } : {}),
|
|
30
30
|
...(bundle.linked ? { linked: true } : {}),
|
|
31
|
+
...(bundle.icon ? { icon: bundle.icon } : {}),
|
|
31
32
|
};
|
|
32
33
|
const fallback = {
|
|
33
34
|
...base,
|
package/src/series/validate.ts
CHANGED
|
@@ -14,8 +14,8 @@ const DRAW_STYLES: readonly SeriesDrawStyle[] = ["line", "area", "step", "column
|
|
|
14
14
|
const PLACEMENTS: readonly SeriesPlacement[] = ["price", "panel"];
|
|
15
15
|
const LINE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
16
16
|
const COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
|
|
17
|
-
/** A chart holds
|
|
18
|
-
const MAX_SERIES_LINES =
|
|
17
|
+
/** A chart holds twenty series: the ticker's price and up to sixteen lines, with room for a few more. */
|
|
18
|
+
const MAX_SERIES_LINES = 16;
|
|
19
19
|
|
|
20
20
|
/** Units with no scale: a percentage of something, not a quantity of it. */
|
|
21
21
|
const RATE_UNITS = ["%", "bp", "x", "ratio", "index", "score", "points"] as const;
|