gloomberb 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -54
- package/package.json +14 -13
- package/patches/{@opentui%2Fcore@0.1.90.patch → @opentui%2Fcore@0.3.2.patch} +175 -90
- package/src/api-client/chat.ts +8 -0
- package/src/api-client/index.test.ts +30 -0
- package/src/api-client/index.ts +4 -0
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/types.ts +1 -0
- package/src/app/global-shortcuts.test.tsx +54 -4
- package/src/app/global-shortcuts.ts +11 -5
- package/src/capabilities/factories.ts +4 -0
- package/src/components/chart/comparison/data.test.ts +54 -0
- package/src/components/chart/comparison/data.ts +47 -24
- package/src/components/chart/core/projection.ts +51 -27
- package/src/components/command-bar/action-runtime.ts +1 -0
- package/src/components/command-bar/layout-items/current-layout.test.ts +115 -0
- package/src/components/command-bar/layout-items/current-layout.ts +33 -0
- package/src/components/command-bar/list/view.tsx +3 -1
- package/src/components/command-bar/pane-templates/items.ts +4 -1
- package/src/components/command-bar/panel/body.tsx +1 -0
- package/src/components/command-bar/routes/root/results.ts +0 -30
- package/src/components/command-bar/routes/root/runtime.ts +2 -20
- package/src/components/command-bar/routes/ticker-search/actions.ts +42 -4
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -1
- package/src/components/command-bar/routing/launch-request.ts +14 -7
- package/src/components/command-bar/routing/list-state.ts +1 -3
- package/src/components/command-bar/surface/index.test.tsx +76 -4
- package/src/components/command-bar/surface/index.tsx +0 -2
- package/src/components/command-bar/surface/pane.test.tsx +1 -0
- package/src/components/command-bar/surface/test-harness.tsx +1 -0
- package/src/components/command-bar/view-model.ts +3 -3
- package/src/components/data-table/stack-view.test.tsx +14 -11
- package/src/components/data-table/stack-view.tsx +1 -2
- package/src/components/data-table/view.test.tsx +24 -18
- package/src/components/data-table/view.tsx +313 -83
- package/src/components/feed-data-table/stack-view.tsx +6 -8
- package/src/components/layout/pane/footer/index.test.tsx +6 -0
- package/src/components/layout/pane/footer/index.tsx +14 -2
- package/src/components/layout/pane/title.ts +5 -1
- package/src/components/layout/shell/index.test.tsx +120 -2
- package/src/components/layout/shell/index.tsx +2 -0
- package/src/components/layout/shell/menu.ts +1 -7
- package/src/components/layout/shell/pane/actions.ts +8 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +12 -2
- package/src/components/layout/shell/shortcuts.ts +14 -6
- package/src/components/layout/shell/window-mode/index.ts +8 -3
- package/src/components/layout/window-edit/presentation.ts +4 -4
- package/src/components/onboarding/onboarding-steps.tsx +3 -2
- package/src/components/pane-settings-dialog/field-dialogs.tsx +0 -2
- package/src/components/pane-settings-dialog/tui.tsx +1 -1
- package/src/components/ticker/list-table-view.tsx +17 -25
- package/src/components/ui/choice-dialog.tsx +12 -2
- package/src/components/ui/multi-select/dialog.tsx +17 -5
- package/src/components/ui/remote-image.tsx +1 -14
- package/src/components/ui/shortcut-hint.tsx +14 -2
- package/src/components/ui/table-layout.test.ts +2 -2
- package/src/components/ui/table-layout.ts +8 -2
- package/src/components/ui/tabs.tsx +115 -86
- package/src/components/ui/ui.test.tsx +83 -4
- package/src/core/state/app/types.ts +12 -4
- package/src/market-data/coordinator/auxiliary.ts +29 -1
- package/src/market-data/coordinator/index.ts +17 -1
- package/src/market-data/hooks.tsx +17 -1
- package/src/market-data/selectors.ts +4 -0
- package/src/plugins/builtin/account-management/form-components.tsx +0 -4
- package/src/plugins/builtin/account-management/model.test.ts +54 -0
- package/src/plugins/builtin/account-management/model.ts +13 -3
- package/src/plugins/builtin/account-management/pane.tsx +5 -11
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +5 -3
- package/src/plugins/builtin/analytics/index.tsx +0 -3
- package/src/plugins/builtin/analytics/view.tsx +6 -5
- package/src/plugins/builtin/broker-manager/index.tsx +6 -6
- package/src/plugins/builtin/buildout/pane/index.tsx +6 -6
- package/src/plugins/builtin/changelog/index.tsx +40 -27
- package/src/plugins/builtin/changelog/model.test.ts +77 -0
- package/src/plugins/builtin/changelog/model.ts +98 -0
- package/src/plugins/builtin/chat/channel-labels.ts +53 -0
- package/src/plugins/builtin/chat/channels.test.ts +28 -0
- package/src/plugins/builtin/chat/channels.ts +36 -19
- package/src/plugins/builtin/chat/chat.test.tsx +264 -5
- package/src/plugins/builtin/chat/content/channel-navigation.ts +50 -11
- package/src/plugins/builtin/chat/content/composer-runtime.test.tsx +144 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +151 -20
- package/src/plugins/builtin/chat/content/composer.tsx +23 -2
- package/src/plugins/builtin/chat/content/index.tsx +66 -4
- package/src/plugins/builtin/chat/content/layout-metrics.ts +3 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +27 -1
- package/src/plugins/builtin/chat/content/transcript.tsx +8 -0
- package/src/plugins/builtin/chat/content/types.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +41 -0
- package/src/plugins/builtin/chat/controller/messages.ts +18 -0
- package/src/plugins/builtin/chat/controller/notifications.ts +10 -4
- package/src/plugins/builtin/chat/controller/utils.ts +6 -3
- package/src/plugins/builtin/chat/controller/view.ts +1 -1
- package/src/plugins/builtin/chat/controller.test.ts +189 -0
- package/src/plugins/builtin/chat/edit-window.ts +24 -0
- package/src/plugins/builtin/chat/layout.ts +94 -31
- package/src/plugins/builtin/chat/message/action-chip.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +53 -20
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +16 -10
- package/src/plugins/builtin/chat/message/render-state.ts +5 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +49 -20
- package/src/plugins/builtin/chat/message/types.ts +2 -0
- package/src/plugins/builtin/chat/pane.tsx +35 -7
- package/src/plugins/builtin/chat/sidebar.test.tsx +72 -0
- package/src/plugins/builtin/chat/status-widget.tsx +29 -11
- package/src/plugins/builtin/chat/test-harness.tsx +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +7 -11
- package/src/plugins/builtin/congress-trades/pane.tsx +14 -22
- package/src/plugins/builtin/earnings/index.tsx +8 -7
- package/src/plugins/builtin/econ/index.tsx +6 -8
- package/src/plugins/builtin/help/components.tsx +1 -1
- package/src/plugins/builtin/help/index.test.tsx +105 -30
- package/src/plugins/builtin/help/index.tsx +326 -164
- package/src/plugins/builtin/help/shortcut-model.ts +5 -6
- package/src/plugins/builtin/holders/pane.tsx +7 -6
- package/src/plugins/builtin/market-movers/index.tsx +6 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +7 -6
- package/src/plugins/builtin/options/view.tsx +16 -8
- package/src/plugins/builtin/portfolio-list/column-values.ts +304 -1
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +93 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +7 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +25 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +281 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +27 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +4 -2
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +10 -68
- package/src/plugins/builtin/research/analyst-pane.tsx +1 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +358 -7
- package/src/plugins/builtin/research/index.test.ts +30 -1
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +6 -8
- package/src/plugins/builtin/sec/filing-documents.ts +66 -0
- package/src/plugins/builtin/sec/index.tsx +132 -20
- package/src/plugins/builtin/sectors/index.tsx +13 -23
- package/src/plugins/builtin/shared/ticker-request.ts +1 -0
- package/src/plugins/builtin/substack/api/auth.ts +185 -0
- package/src/plugins/builtin/substack/api/cache.ts +78 -0
- package/src/plugins/builtin/substack/api/loaders.ts +146 -0
- package/src/plugins/builtin/substack/api/store.ts +186 -0
- package/src/plugins/builtin/substack/api/types.ts +40 -0
- package/src/plugins/builtin/substack/api.test.ts +298 -0
- package/src/plugins/builtin/substack/article-detail.tsx +359 -0
- package/src/plugins/builtin/substack/article-stack.tsx +129 -0
- package/src/plugins/builtin/substack/content.ts +478 -0
- package/src/plugins/builtin/substack/feed-tabs.tsx +41 -0
- package/src/plugins/builtin/substack/index.tsx +49 -0
- package/src/plugins/builtin/substack/login-view.tsx +126 -0
- package/src/plugins/builtin/substack/model.test.ts +155 -0
- package/src/plugins/builtin/substack/normalize.ts +343 -0
- package/src/plugins/builtin/substack/pane-footer.ts +71 -0
- package/src/plugins/builtin/substack/pane-state.ts +170 -0
- package/src/plugins/builtin/substack/pane.tsx +500 -0
- package/src/plugins/builtin/substack/table.ts +111 -0
- package/src/plugins/builtin/substack/types.ts +71 -0
- package/src/plugins/builtin/substack/utils.ts +131 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +26 -25
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +6 -5
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +10 -8
- package/src/plugins/builtin/ticker-detail/index.test.tsx +16 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/pane.tsx +13 -1
- package/src/plugins/builtin/world-indices/index.tsx +9 -6
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/pane-manager/layout-state.ts +11 -0
- package/src/plugins/pane-manager.ts +1 -1
- package/src/plugins/prediction-markets/pane.tsx +10 -9
- package/src/plugins/registry/index.ts +3 -0
- package/src/plugins/runtime/context.tsx +1 -0
- package/src/plugins/runtime/index.tsx +4 -0
- package/src/react/use-throttled-commit-value.ts +116 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +42 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +3 -2
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -2
- package/src/renderers/electrobun/view/host/input.tsx +23 -2
- package/src/renderers/electrobun/view/http-fetch.ts +10 -3
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +4 -0
- package/src/sources/provider-router/cache.ts +1 -0
- package/src/sources/provider-router/document-routes.ts +45 -0
- package/src/sources/provider-router/index.ts +4 -0
- package/src/sources/sec-edgar/content.ts +9 -3
- package/src/sources/sec-edgar.test.ts +108 -1
- package/src/sources/sec-edgar.ts +105 -1
- package/src/sources/yahoo-finance.ts +4 -0
- package/src/state/app/context/index.test.tsx +16 -0
- package/src/test-support/plugin-runtime.ts +1 -0
- package/src/tickers/search/index.ts +1 -0
- package/src/types/data-provider.ts +11 -0
- package/src/ui/host.tsx +1 -0
- package/src/version.ts +1 -1
|
@@ -61,10 +61,11 @@ function createShellPluginRegistry(options?: {
|
|
|
61
61
|
} as unknown as PluginRegistry;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
async function emitKeypress(event: { name?: string; sequence?: string; ctrl?: boolean; meta?: boolean; shift?: boolean }) {
|
|
64
|
+
async function emitKeypress(event: { name?: string; sequence?: string; ctrl?: boolean; meta?: boolean; shift?: boolean; alt?: boolean }) {
|
|
65
65
|
await act(async () => {
|
|
66
66
|
const keyEvent = {
|
|
67
67
|
ctrl: false,
|
|
68
|
+
alt: false,
|
|
68
69
|
meta: false,
|
|
69
70
|
option: false,
|
|
70
71
|
shift: false,
|
|
@@ -312,6 +313,7 @@ describe("Shell", () => {
|
|
|
312
313
|
const frame = testSetup.captureCharFrame();
|
|
313
314
|
expect(frame).toContain("Settings");
|
|
314
315
|
expect(frame).toContain("Ctrl+,");
|
|
316
|
+
expect(frame).not.toContain("Layout Actions");
|
|
315
317
|
expect(frame).not.toContain("CmdOrCtrl");
|
|
316
318
|
});
|
|
317
319
|
|
|
@@ -400,24 +402,92 @@ describe("Shell", () => {
|
|
|
400
402
|
});
|
|
401
403
|
await testSetup.renderOnce();
|
|
402
404
|
|
|
403
|
-
|
|
405
|
+
const frame = testSetup.captureCharFrame();
|
|
406
|
+
expect(frame).toContain("Dock Pane");
|
|
407
|
+
expect(frame).not.toContain("Layout Actions");
|
|
404
408
|
});
|
|
405
409
|
|
|
406
410
|
test("resolves pane management shortcuts", () => {
|
|
407
411
|
const base = { ctrl: false, meta: true, super: true, shift: true, alt: false };
|
|
408
412
|
expect(resolvePaneManagementShortcut({ ...base, name: ",", key: ",", shift: false })).toBe("settings");
|
|
409
413
|
expect(resolvePaneManagementShortcut({ ...base, name: "w", key: "w", ctrl: true, meta: false, super: false, shift: false })).toBe("close");
|
|
414
|
+
expect(resolvePaneManagementShortcut({ ...base, name: "w", key: "w", shift: false, alt: true })).toBe("close-all-floating");
|
|
415
|
+
expect(resolvePaneManagementShortcut({ ...base, name: "W", key: "W", shift: false })).toBeNull();
|
|
410
416
|
expect(resolvePaneManagementShortcut({ ...base, name: "D", key: "D" })).toBe("toggle-floating");
|
|
411
417
|
expect(resolvePaneManagementShortcut({ ...base, name: "o", key: "o" })).toBe("pop-out");
|
|
412
418
|
expect(resolvePaneManagementShortcut({ ...base, name: "c", key: "c" })).toBe("copy-screenshot");
|
|
413
419
|
expect(resolvePaneManagementShortcut({ ...base, name: "l", key: "l" })).toBe("layout-actions");
|
|
414
420
|
expect(resolvePaneManagementShortcut({ ...base, name: "g", key: "g" })).toBe("gridlock-all");
|
|
415
421
|
expect(resolvePaneManagementShortcut({ ...base, name: "m", key: "m" })).toBe("window-mode");
|
|
422
|
+
expect(resolvePaneManagementShortcut({ ...base, name: "r", key: "r" })).toBe("window-resize-mode");
|
|
416
423
|
expect(resolvePaneManagementShortcut({ ...base, name: "n", key: "n" })).toBeNull();
|
|
417
424
|
expect(resolvePaneManagementShortcut({ ...base, name: "d", key: "d", alt: true })).toBeNull();
|
|
418
425
|
expect(resolvePaneManagementShortcut({ ...base, name: "d", key: "d", meta: false, super: false })).toBeNull();
|
|
419
426
|
});
|
|
420
427
|
|
|
428
|
+
test("exits window mode on Enter without changes", async () => {
|
|
429
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-window-mode-resize-shortcut-test");
|
|
430
|
+
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|
|
431
|
+
const detailPane = requireLayoutInstance(config, "ticker-detail:main");
|
|
432
|
+
const dockedLayout = {
|
|
433
|
+
dockRoot: {
|
|
434
|
+
kind: "split" as const,
|
|
435
|
+
axis: "horizontal" as const,
|
|
436
|
+
ratio: 0.5,
|
|
437
|
+
first: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
438
|
+
second: { kind: "pane" as const, instanceId: "ticker-detail:main" },
|
|
439
|
+
},
|
|
440
|
+
instances: [{ ...mainPane }, { ...detailPane }],
|
|
441
|
+
floating: [],
|
|
442
|
+
detached: [],
|
|
443
|
+
};
|
|
444
|
+
const { actions } = await renderShellForWindowModeTest(
|
|
445
|
+
createShellStateWithLayout(config, dockedLayout, "portfolio-list:main"),
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
await emitKeypress({ name: "m", ctrl: true, shift: true });
|
|
449
|
+
expect(testSetup.captureCharFrame()).toContain("WINDOW MOVE");
|
|
450
|
+
|
|
451
|
+
await emitKeypress({ name: "enter" });
|
|
452
|
+
await act(async () => {
|
|
453
|
+
await testSetup!.renderOnce();
|
|
454
|
+
});
|
|
455
|
+
expect(testSetup.captureCharFrame()).not.toContain("WINDOW MOVE");
|
|
456
|
+
expect(actions.some((action) => action.type === "UPDATE_LAYOUT")).toBe(false);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("starts resize mode directly from the resize shortcut", async () => {
|
|
460
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-window-mode-resize-shortcut-test");
|
|
461
|
+
const mainPane = requireLayoutInstance(config, "portfolio-list:main");
|
|
462
|
+
const detailPane = requireLayoutInstance(config, "ticker-detail:main");
|
|
463
|
+
const dockedLayout = {
|
|
464
|
+
dockRoot: {
|
|
465
|
+
kind: "split" as const,
|
|
466
|
+
axis: "horizontal" as const,
|
|
467
|
+
ratio: 0.5,
|
|
468
|
+
first: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
469
|
+
second: { kind: "pane" as const, instanceId: "ticker-detail:main" },
|
|
470
|
+
},
|
|
471
|
+
instances: [{ ...mainPane }, { ...detailPane }],
|
|
472
|
+
floating: [],
|
|
473
|
+
detached: [],
|
|
474
|
+
};
|
|
475
|
+
const { actions } = await renderShellForWindowModeTest(
|
|
476
|
+
createShellStateWithLayout(config, dockedLayout, "portfolio-list:main"),
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
await emitKeypress({ name: "r", ctrl: true, shift: true });
|
|
480
|
+
expect(testSetup.captureCharFrame()).toContain("WINDOW RESIZE");
|
|
481
|
+
|
|
482
|
+
await emitKeypress({ name: "enter" });
|
|
483
|
+
await act(async () => {
|
|
484
|
+
await testSetup!.renderOnce();
|
|
485
|
+
});
|
|
486
|
+
const frame = testSetup.captureCharFrame();
|
|
487
|
+
expect(frame).not.toContain("WINDOW RESIZE");
|
|
488
|
+
expect(actions.some((action) => action.type === "UPDATE_LAYOUT")).toBe(false);
|
|
489
|
+
});
|
|
490
|
+
|
|
421
491
|
test("moves a floating pane in window mode and commits once", async () => {
|
|
422
492
|
const config = createDefaultConfig("/tmp/gloomberb-shell-window-mode-test");
|
|
423
493
|
const floatingLayout = cloneLayout(config.layout);
|
|
@@ -785,4 +855,52 @@ describe("Shell", () => {
|
|
|
785
855
|
expect(updateLayout?.layout.floating).toEqual([]);
|
|
786
856
|
});
|
|
787
857
|
|
|
858
|
+
test("closes all floating panes with Ctrl+Alt+W", async () => {
|
|
859
|
+
const config = createDefaultConfig("/tmp/gloomberb-shell-test");
|
|
860
|
+
const mainPane = config.layout.instances.find((instance) => instance.instanceId === "portfolio-list:main");
|
|
861
|
+
const firstDetailPane = config.layout.instances.find((instance) => instance.instanceId === "ticker-detail:main");
|
|
862
|
+
if (!mainPane || !firstDetailPane) throw new Error("missing default panes");
|
|
863
|
+
const secondDetailPane = {
|
|
864
|
+
...firstDetailPane,
|
|
865
|
+
instanceId: "ticker-detail:secondary",
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const mixedLayout = {
|
|
869
|
+
dockRoot: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
870
|
+
instances: [{ ...mainPane }, { ...firstDetailPane }, secondDetailPane],
|
|
871
|
+
floating: [
|
|
872
|
+
{ instanceId: "ticker-detail:main", x: 4, y: 2, width: 30, height: 8 },
|
|
873
|
+
{ instanceId: "ticker-detail:secondary", x: 8, y: 3, width: 30, height: 8 },
|
|
874
|
+
],
|
|
875
|
+
detached: [],
|
|
876
|
+
};
|
|
877
|
+
const state = {
|
|
878
|
+
...createInitialState({
|
|
879
|
+
...config,
|
|
880
|
+
layout: cloneLayout(mixedLayout),
|
|
881
|
+
layouts: [{ name: "Default", layout: cloneLayout(mixedLayout) }],
|
|
882
|
+
}),
|
|
883
|
+
focusedPaneId: "portfolio-list:main",
|
|
884
|
+
};
|
|
885
|
+
const actions: Array<any> = [];
|
|
886
|
+
|
|
887
|
+
testSetup = await testRender(
|
|
888
|
+
<AppContext value={{ state, dispatch: (action) => actions.push(action) }}>
|
|
889
|
+
<TestDialogProvider>
|
|
890
|
+
<Shell pluginRegistry={createShellPluginRegistry()} />
|
|
891
|
+
</TestDialogProvider>
|
|
892
|
+
</AppContext>,
|
|
893
|
+
{ width: 40, height: 12 },
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
await testSetup.renderOnce();
|
|
897
|
+
await emitKeypress({ name: "w", ctrl: true, alt: true });
|
|
898
|
+
|
|
899
|
+
const updateLayout = actions.find((action) => action.type === "UPDATE_LAYOUT");
|
|
900
|
+
expect(actions).toContainEqual({ type: "PUSH_LAYOUT_HISTORY" });
|
|
901
|
+
expect(updateLayout?.layout.instances.map((instance: { instanceId: string }) => instance.instanceId)).toEqual(["portfolio-list:main"]);
|
|
902
|
+
expect(updateLayout?.layout.floating).toEqual([]);
|
|
903
|
+
expect(updateLayout?.layout.dockRoot).toEqual({ kind: "pane", instanceId: "portfolio-list:main" });
|
|
904
|
+
});
|
|
905
|
+
|
|
788
906
|
});
|
|
@@ -186,6 +186,7 @@ export function Shell({
|
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
const {
|
|
189
|
+
closeAllFloatingPanes,
|
|
189
190
|
closeFocusedPane,
|
|
190
191
|
copyFocusedPaneScreenshot,
|
|
191
192
|
copyPaneScreenshot,
|
|
@@ -213,6 +214,7 @@ export function Shell({
|
|
|
213
214
|
|
|
214
215
|
useShellPaneManagementShortcuts({
|
|
215
216
|
cancelActiveDrag,
|
|
217
|
+
closeAllFloatingPanes,
|
|
216
218
|
closeFocusedPane,
|
|
217
219
|
copyFocusedPaneScreenshot,
|
|
218
220
|
focusedPaneId,
|
|
@@ -101,15 +101,9 @@ export function menuForPane(
|
|
|
101
101
|
{
|
|
102
102
|
id: "window-resize-mode",
|
|
103
103
|
label: "Resize Window...",
|
|
104
|
-
accelerator:
|
|
104
|
+
accelerator: PANE_MANAGEMENT_ACCELERATORS.windowResizeMode,
|
|
105
105
|
onSelect: () => pluginRegistry.openWindowMode(pane.instance.instanceId, "resize"),
|
|
106
106
|
},
|
|
107
|
-
{
|
|
108
|
-
id: "layout-actions",
|
|
109
|
-
label: "Layout Actions...",
|
|
110
|
-
accelerator: PANE_MANAGEMENT_ACCELERATORS.layoutActions,
|
|
111
|
-
onSelect: () => pluginRegistry.openCommandBar("LAY "),
|
|
112
|
-
},
|
|
113
107
|
);
|
|
114
108
|
|
|
115
109
|
return baseActions;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
floatPane,
|
|
6
6
|
gridlockAllPanes,
|
|
7
7
|
isPaneInLayout,
|
|
8
|
+
removeFloatingPanes,
|
|
8
9
|
removePane,
|
|
9
10
|
type ResolvedPane,
|
|
10
11
|
} from "../../../../plugins/pane-manager";
|
|
@@ -73,6 +74,12 @@ export function useShellPaneActions({
|
|
|
73
74
|
return true;
|
|
74
75
|
}, [focusedPaneId, persistLayout, visibleLayout]);
|
|
75
76
|
|
|
77
|
+
const closeAllFloatingPanes = useCallback(() => {
|
|
78
|
+
if (visibleLayout.floating.length === 0) return false;
|
|
79
|
+
persistLayout(removeFloatingPanes(visibleLayout));
|
|
80
|
+
return true;
|
|
81
|
+
}, [persistLayout, visibleLayout]);
|
|
82
|
+
|
|
76
83
|
const copyFocusedPaneScreenshot = useCallback(() => {
|
|
77
84
|
if (!focusedPaneId || !nativePaneChrome || !rendererHost.copyPngImage) return false;
|
|
78
85
|
if (!paneMap.has(focusedPaneId)) return false;
|
|
@@ -115,6 +122,7 @@ export function useShellPaneActions({
|
|
|
115
122
|
}, [persistLayout, visibleLayout]);
|
|
116
123
|
|
|
117
124
|
return {
|
|
125
|
+
closeAllFloatingPanes,
|
|
118
126
|
closeFocusedPane,
|
|
119
127
|
copyFocusedPaneScreenshot,
|
|
120
128
|
copyPaneScreenshot,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
2
|
import { useShortcut } from "../../../../react/input";
|
|
3
|
+
import type { WindowEditMode } from "../../../../plugins/registry";
|
|
3
4
|
import {
|
|
4
5
|
createDoubleEscapeCloseState,
|
|
5
6
|
recordDoubleEscapeClose,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
|
|
13
14
|
interface ShellPaneManagementShortcutOptions {
|
|
14
15
|
cancelActiveDrag(): void;
|
|
16
|
+
closeAllFloatingPanes(): boolean;
|
|
15
17
|
closeFocusedPane(): boolean;
|
|
16
18
|
copyFocusedPaneScreenshot(): boolean;
|
|
17
19
|
focusedPaneId: string | null;
|
|
@@ -22,12 +24,13 @@ interface ShellPaneManagementShortcutOptions {
|
|
|
22
24
|
openLayoutMenu(): void;
|
|
23
25
|
overlayOpen: boolean;
|
|
24
26
|
popOutFocusedPane(): boolean;
|
|
25
|
-
startWindowMode(): void;
|
|
27
|
+
startWindowMode(paneId?: string, mode?: WindowEditMode): void;
|
|
26
28
|
toggleFocusedPaneFloating(): boolean;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export function useShellPaneManagementShortcuts({
|
|
30
32
|
cancelActiveDrag,
|
|
33
|
+
closeAllFloatingPanes,
|
|
31
34
|
closeFocusedPane,
|
|
32
35
|
copyFocusedPaneScreenshot,
|
|
33
36
|
focusedPaneId,
|
|
@@ -82,6 +85,9 @@ export function useShellPaneManagementShortcuts({
|
|
|
82
85
|
case "close":
|
|
83
86
|
handled = closeFocusedPane();
|
|
84
87
|
break;
|
|
88
|
+
case "close-all-floating":
|
|
89
|
+
handled = closeAllFloatingPanes();
|
|
90
|
+
break;
|
|
85
91
|
case "settings":
|
|
86
92
|
handled = openFocusedPaneSettings();
|
|
87
93
|
break;
|
|
@@ -102,7 +108,11 @@ export function useShellPaneManagementShortcuts({
|
|
|
102
108
|
handled = gridlockVisiblePanes();
|
|
103
109
|
break;
|
|
104
110
|
case "window-mode":
|
|
105
|
-
startWindowMode();
|
|
111
|
+
startWindowMode(undefined, "move");
|
|
112
|
+
handled = true;
|
|
113
|
+
break;
|
|
114
|
+
case "window-resize-mode":
|
|
115
|
+
startWindowMode(undefined, "resize");
|
|
106
116
|
handled = true;
|
|
107
117
|
break;
|
|
108
118
|
}
|
|
@@ -6,9 +6,11 @@ export const PANE_MANAGEMENT_ACCELERATORS = {
|
|
|
6
6
|
popOut: "CmdOrCtrl+Shift+O",
|
|
7
7
|
copyScreenshot: "CmdOrCtrl+Shift+C",
|
|
8
8
|
close: "CmdOrCtrl+W",
|
|
9
|
+
closeAllFloating: "CmdOrCtrl+Alt+W",
|
|
9
10
|
layoutActions: "CmdOrCtrl+Shift+L",
|
|
10
11
|
gridlockAll: "CmdOrCtrl+Shift+G",
|
|
11
12
|
windowMode: "CmdOrCtrl+Shift+M",
|
|
13
|
+
windowResizeMode: "CmdOrCtrl+Shift+R",
|
|
12
14
|
} as const;
|
|
13
15
|
|
|
14
16
|
export type PaneManagementShortcut =
|
|
@@ -17,24 +19,30 @@ export type PaneManagementShortcut =
|
|
|
17
19
|
| "pop-out"
|
|
18
20
|
| "copy-screenshot"
|
|
19
21
|
| "close"
|
|
22
|
+
| "close-all-floating"
|
|
20
23
|
| "layout-actions"
|
|
21
24
|
| "gridlock-all"
|
|
22
|
-
| "window-mode"
|
|
25
|
+
| "window-mode"
|
|
26
|
+
| "window-resize-mode";
|
|
23
27
|
|
|
24
28
|
export function resolvePaneManagementShortcut(
|
|
25
29
|
event: Pick<KeyEventLike, "name" | "key" | "ctrl" | "meta" | "super" | "shift" | "alt">,
|
|
26
30
|
): PaneManagementShortcut | null {
|
|
27
31
|
if (!event.ctrl && !event.meta && !event.super) return null;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!
|
|
32
|
+
const rawName = event.name ?? event.key ?? "";
|
|
33
|
+
const name = rawName.toLowerCase();
|
|
34
|
+
const shifted = event.shift || rawName !== name;
|
|
35
|
+
if (!shifted && event.alt && name === "w") return "close-all-floating";
|
|
36
|
+
if (!shifted && name === "w") return "close";
|
|
37
|
+
if (!shifted && name === ",") return "settings";
|
|
38
|
+
if (!shifted || event.alt) return null;
|
|
32
39
|
if (name === "c") return "copy-screenshot";
|
|
33
40
|
if (name === "d") return "toggle-floating";
|
|
34
41
|
if (name === "o") return "pop-out";
|
|
35
42
|
if (name === "l") return "layout-actions";
|
|
36
43
|
if (name === "g") return "gridlock-all";
|
|
37
44
|
if (name === "m") return "window-mode";
|
|
45
|
+
if (name === "r") return "window-resize-mode";
|
|
38
46
|
return null;
|
|
39
47
|
}
|
|
40
48
|
|
|
@@ -42,6 +50,6 @@ export function inputCaptureAllowsPaneManagementShortcut(
|
|
|
42
50
|
shortcut: PaneManagementShortcut,
|
|
43
51
|
event: Pick<KeyEventLike, "meta" | "super" | "targetEditable">,
|
|
44
52
|
): boolean {
|
|
45
|
-
if (shortcut !== "close") return false;
|
|
53
|
+
if (shortcut !== "close" && shortcut !== "close-all-floating") return false;
|
|
46
54
|
return event.meta || event.super || event.targetEditable !== true;
|
|
47
55
|
}
|
|
@@ -128,9 +128,13 @@ export function useShellWindowMode({
|
|
|
128
128
|
if (!windowMode) return;
|
|
129
129
|
const committedLayout = resolveWindowEditCommitLayout(windowMode, bounds, dockGeometryOptions);
|
|
130
130
|
const hasPendingCommit = windowEditHasPendingCommit(windowMode, bounds, dockGeometryOptions);
|
|
131
|
-
if (hasPendingCommit) {
|
|
132
|
-
|
|
131
|
+
if (!hasPendingCommit) {
|
|
132
|
+
focusPane(windowMode.paneId);
|
|
133
|
+
setWindowMode(null);
|
|
134
|
+
return;
|
|
133
135
|
}
|
|
136
|
+
|
|
137
|
+
persistLayout(committedLayout);
|
|
134
138
|
focusPane(windowMode.paneId);
|
|
135
139
|
setWindowMode({
|
|
136
140
|
paneId: windowMode.paneId,
|
|
@@ -138,7 +142,7 @@ export function useShellWindowMode({
|
|
|
138
142
|
mode: "move",
|
|
139
143
|
focus: normalizeWindowEditFocus({ kind: "move" }, committedLayout, windowMode.paneId, "move", bounds, dockGeometryOptions),
|
|
140
144
|
dirty: false,
|
|
141
|
-
notice:
|
|
145
|
+
notice: "Committed",
|
|
142
146
|
});
|
|
143
147
|
}, [bounds, dockGeometryOptions, focusPane, persistLayout, windowMode]);
|
|
144
148
|
|
|
@@ -166,6 +170,7 @@ export function useShellWindowMode({
|
|
|
166
170
|
|
|
167
171
|
useShortcut((event) => {
|
|
168
172
|
if (!windowMode) return;
|
|
173
|
+
if (event.ctrl || event.meta || event.super || event.alt) return;
|
|
169
174
|
const name = (event.name ?? event.key ?? "").toLowerCase();
|
|
170
175
|
let handled = true;
|
|
171
176
|
|
|
@@ -73,14 +73,14 @@ export function windowEditStatusLine(
|
|
|
73
73
|
export function windowEditHelpText(state: WindowEditState): string {
|
|
74
74
|
if (state.mode === "move") {
|
|
75
75
|
if (state.focus.kind === "dock-move") {
|
|
76
|
-
return "arrows/hjkl choose side m retarget Tab/w window d float r resize Enter commit Esc exit/cancel";
|
|
76
|
+
return "arrows/hjkl choose side m retarget Tab/w window d float r resize Enter commit/exit Esc exit/cancel";
|
|
77
77
|
}
|
|
78
|
-
return "Tab/w window d dock/float r resize arrows/hjkl target/move Shift fast Enter commit Esc exit/cancel";
|
|
78
|
+
return "Tab/w window d dock/float r resize arrows/hjkl target/move Shift fast Enter commit/exit Esc exit/cancel";
|
|
79
79
|
}
|
|
80
80
|
if (state.focus.kind === "floating-resize") {
|
|
81
|
-
return "Tab corner w window m move arrows/hjkl resize Shift fast Enter commit Esc exit/cancel";
|
|
81
|
+
return "Tab corner w window m move arrows/hjkl resize Shift fast Enter commit/exit Esc exit/cancel";
|
|
82
82
|
}
|
|
83
|
-
return "arrows/hjkl move divider Tab divider w window m move Shift fast Enter commit Esc exit/cancel";
|
|
83
|
+
return "arrows/hjkl move divider Tab divider w window m move Shift fast Enter commit/exit Esc exit/cancel";
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
export function resolveWindowEditDockMovePreview(
|
|
@@ -101,7 +101,8 @@ export function ShortcutsStep({ pluginRegistry }: { pluginRegistry: PluginRegist
|
|
|
101
101
|
const shortcutDisplayMode = getShortcutDisplayMode(uiHost.kind);
|
|
102
102
|
const platformShortcut = (keys: string | readonly string[]) => formatPrimaryShortcut(keys, shortcutPlatform, shortcutDisplayMode);
|
|
103
103
|
const keyboardShortcuts = [
|
|
104
|
-
{ key: "Ctrl+P
|
|
104
|
+
{ key: "Ctrl+P", desc: "Open command mode" },
|
|
105
|
+
{ key: "`", desc: "Open ticker search" },
|
|
105
106
|
{ key: "Tab", desc: "Switch between panels" },
|
|
106
107
|
{ key: platformShortcut("W"), desc: "Close the focused pane" },
|
|
107
108
|
{ key: platformShortcut(["Shift", "D"]), desc: "Dock or float the focused pane" },
|
|
@@ -155,7 +156,7 @@ export function ShortcutsStep({ pluginRegistry }: { pluginRegistry: PluginRegist
|
|
|
155
156
|
</Box>
|
|
156
157
|
<Box height={1} />
|
|
157
158
|
<Box height={1}>
|
|
158
|
-
<Text fg={colors.textDim}>{"Type these in the command bar ("}<Span fg={colors.text}><Strong>{"Ctrl+P"}</Strong></Span>{"
|
|
159
|
+
<Text fg={colors.textDim}>{"Type these in the command bar ("}<Span fg={colors.text}><Strong>{"Ctrl+P"}</Strong></Span>{"):"}</Text>
|
|
159
160
|
</Box>
|
|
160
161
|
<Box height={1} />
|
|
161
162
|
|
|
@@ -61,7 +61,6 @@ export function TuiSelectFieldDialog(props: SelectFieldDialogProps) {
|
|
|
61
61
|
return (
|
|
62
62
|
<DialogFrame
|
|
63
63
|
title={field.label}
|
|
64
|
-
footer="Use ↑↓ to choose · enter to apply · esc cancel"
|
|
65
64
|
>
|
|
66
65
|
<ListView
|
|
67
66
|
items={field.options.map((option) => ({
|
|
@@ -158,7 +157,6 @@ function TuiTextFieldDialog(props: TextFieldDialogProps) {
|
|
|
158
157
|
return (
|
|
159
158
|
<DialogFrame
|
|
160
159
|
title={field.label}
|
|
161
|
-
footer="Enter to apply · esc cancel"
|
|
162
160
|
>
|
|
163
161
|
<Box flexDirection="column" gap={1}>
|
|
164
162
|
<TextField
|
|
@@ -29,7 +29,7 @@ export function TuiPaneSettingsDialogBody({
|
|
|
29
29
|
onActivate: (field: PaneSettingField | undefined) => void;
|
|
30
30
|
}) {
|
|
31
31
|
return (
|
|
32
|
-
<DialogFrame title={title}
|
|
32
|
+
<DialogFrame title={title}>
|
|
33
33
|
<ListView
|
|
34
34
|
items={fields.map((field) => ({
|
|
35
35
|
id: field.key,
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
useEffect,
|
|
4
4
|
useMemo,
|
|
5
5
|
useRef,
|
|
6
|
-
useState,
|
|
7
6
|
type ReactNode,
|
|
8
7
|
type RefObject,
|
|
9
8
|
} from "react";
|
|
@@ -61,8 +60,6 @@ export interface TickerListTableViewProps {
|
|
|
61
60
|
setHoveredIdx?: (index: number | null) => void;
|
|
62
61
|
keyboardNavigation?: boolean;
|
|
63
62
|
onRootKeyDown?: (event: DataTableKeyEvent) => boolean | void;
|
|
64
|
-
onSelectIndex?: (index: number, ticker: TickerRecord) => void;
|
|
65
|
-
onActivateIndex?: (index: number, ticker: TickerRecord) => void;
|
|
66
63
|
onVisibleRangeChange?: (range: TickerListVisibleRange) => void;
|
|
67
64
|
visibleRangeBuffer?: number;
|
|
68
65
|
resetScrollKey?: unknown;
|
|
@@ -84,12 +81,18 @@ const FLASHABLE_QUOTE_COLUMN_IDS = new Set([
|
|
|
84
81
|
"bid",
|
|
85
82
|
"ask",
|
|
86
83
|
"spread",
|
|
84
|
+
"spread_pct",
|
|
87
85
|
"ext_hours",
|
|
88
86
|
"market_cap",
|
|
87
|
+
"volume",
|
|
88
|
+
"dollar_volume",
|
|
89
|
+
"range_52w",
|
|
89
90
|
"mkt_value",
|
|
91
|
+
"weight",
|
|
90
92
|
"day_pnl",
|
|
91
93
|
"pnl",
|
|
92
94
|
"pnl_pct",
|
|
95
|
+
"mark_delta",
|
|
93
96
|
]);
|
|
94
97
|
|
|
95
98
|
const EMPTY_FLASH_SYMBOLS = new Map<string, QuoteFlashDirection>();
|
|
@@ -120,8 +123,6 @@ export function TickerListTableView({
|
|
|
120
123
|
setHoveredIdx,
|
|
121
124
|
keyboardNavigation = true,
|
|
122
125
|
onRootKeyDown,
|
|
123
|
-
onSelectIndex,
|
|
124
|
-
onActivateIndex,
|
|
125
126
|
onVisibleRangeChange,
|
|
126
127
|
visibleRangeBuffer = 0,
|
|
127
128
|
resetScrollKey,
|
|
@@ -153,8 +154,6 @@ export function TickerListTableView({
|
|
|
153
154
|
() => tickers.findIndex((ticker) => ticker.metadata.ticker === cursorSymbol),
|
|
154
155
|
[cursorSymbol, tickers],
|
|
155
156
|
);
|
|
156
|
-
const [scrollToIndex, setScrollToIndex] = useState<number | null>(null);
|
|
157
|
-
const [scrollToIndexVersion, setScrollToIndexVersion] = useState(0);
|
|
158
157
|
const lastScrollCursorSymbolRef = useRef<string | null | undefined>(undefined);
|
|
159
158
|
|
|
160
159
|
const emitVisibleRange = useCallback(() => {
|
|
@@ -177,16 +176,12 @@ export function TickerListTableView({
|
|
|
177
176
|
useEffect(() => {
|
|
178
177
|
const shouldScrollToCursor = lastScrollCursorSymbolRef.current !== cursorSymbol;
|
|
179
178
|
lastScrollCursorSymbolRef.current = cursorSymbol;
|
|
180
|
-
if (shouldScrollToCursor && selectedIndex >= 0)
|
|
181
|
-
setScrollToIndex(selectedIndex);
|
|
182
|
-
setScrollToIndexVersion((current) => current + 1);
|
|
183
|
-
}
|
|
184
|
-
queueMicrotask(emitVisibleRange);
|
|
179
|
+
if (shouldScrollToCursor && selectedIndex >= 0) queueMicrotask(emitVisibleRange);
|
|
185
180
|
}, [cursorSymbol, emitVisibleRange, selectedIndex]);
|
|
186
181
|
|
|
187
182
|
useEffect(() => {
|
|
188
183
|
queueMicrotask(emitVisibleRange);
|
|
189
|
-
}, [emitVisibleRange
|
|
184
|
+
}, [emitVisibleRange]);
|
|
190
185
|
|
|
191
186
|
const renderCell = useCallback((
|
|
192
187
|
ticker: TickerRecord,
|
|
@@ -236,18 +231,16 @@ export function TickerListTableView({
|
|
|
236
231
|
}, [financialsMap, renderer, showContextMenu]);
|
|
237
232
|
|
|
238
233
|
const handleRowMouseDown = useCallback((ticker: TickerRecord, _index: number, event: TableMouseEvent) => {
|
|
239
|
-
setCursorSymbol(ticker.metadata.ticker);
|
|
240
234
|
if (event.button !== 2) return false;
|
|
241
235
|
if (nativeContextMenu !== true) {
|
|
242
236
|
showTickerContextMenu(ticker, event);
|
|
243
237
|
}
|
|
244
238
|
return true;
|
|
245
|
-
}, [nativeContextMenu,
|
|
239
|
+
}, [nativeContextMenu, showTickerContextMenu]);
|
|
246
240
|
|
|
247
241
|
const handleRowContextMenu = useCallback((ticker: TickerRecord, _index: number, event: TableMouseEvent) => {
|
|
248
|
-
setCursorSymbol(ticker.metadata.ticker);
|
|
249
242
|
showTickerContextMenu(ticker, event);
|
|
250
|
-
}, [
|
|
243
|
+
}, [showTickerContextMenu]);
|
|
251
244
|
|
|
252
245
|
return (
|
|
253
246
|
<DataTableView<TickerRecord, ColumnConfig>
|
|
@@ -258,9 +251,13 @@ export function TickerListTableView({
|
|
|
258
251
|
sortDirection={sortDirection}
|
|
259
252
|
onHeaderClick={onHeaderClick ?? (() => {})}
|
|
260
253
|
getItemKey={(ticker) => ticker.metadata.ticker}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
254
|
+
selection={{
|
|
255
|
+
kind: "id",
|
|
256
|
+
selectedId: cursorSymbol,
|
|
257
|
+
getId: (ticker) => ticker.metadata.ticker,
|
|
258
|
+
onChange: (symbol) => {
|
|
259
|
+
setCursorSymbol(symbol);
|
|
260
|
+
},
|
|
264
261
|
}}
|
|
265
262
|
onActivate={(ticker) => {
|
|
266
263
|
onRowActivate?.(ticker);
|
|
@@ -273,9 +270,6 @@ export function TickerListTableView({
|
|
|
273
270
|
emptyStateHint={emptyHint}
|
|
274
271
|
virtualize={virtualize}
|
|
275
272
|
overscan={overscan}
|
|
276
|
-
selectedIndex={selectedIndex}
|
|
277
|
-
onSelectIndex={onSelectIndex}
|
|
278
|
-
onActivateIndex={onActivateIndex}
|
|
279
273
|
rootBefore={rootBefore}
|
|
280
274
|
rootAfter={rootAfter}
|
|
281
275
|
rootWidth={rootWidth}
|
|
@@ -290,8 +284,6 @@ export function TickerListTableView({
|
|
|
290
284
|
keyboardNavigation={keyboardNavigation}
|
|
291
285
|
onRootKeyDown={onRootKeyDown}
|
|
292
286
|
resetScrollKey={resetScrollKey}
|
|
293
|
-
scrollToIndex={scrollToIndex}
|
|
294
|
-
scrollToIndexVersion={scrollToIndexVersion}
|
|
295
287
|
/>
|
|
296
288
|
);
|
|
297
289
|
}
|
|
@@ -17,6 +17,7 @@ export interface ChoiceDialogChoice {
|
|
|
17
17
|
export interface ChoiceDialogProps extends PromptContext<string> {
|
|
18
18
|
title: string;
|
|
19
19
|
choices: ChoiceDialogChoice[];
|
|
20
|
+
selectedChoiceId?: string;
|
|
20
21
|
footer?: string;
|
|
21
22
|
bgColor?: string;
|
|
22
23
|
}
|
|
@@ -30,14 +31,23 @@ function getChoiceDescription(choice: ChoiceDialogChoice | undefined): string {
|
|
|
30
31
|
return choice?.description ?? "";
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
function getInitialChoiceIndex(choices: ChoiceDialogChoice[], selectedChoiceId: string | undefined): number {
|
|
35
|
+
if (!selectedChoiceId) return 0;
|
|
36
|
+
const selectedIndex = choices.findIndex((choice) => choice.id === selectedChoiceId);
|
|
37
|
+
return selectedIndex >= 0 ? selectedIndex : 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
export function ChoiceDialog({
|
|
34
41
|
resolve,
|
|
35
42
|
title,
|
|
36
43
|
choices,
|
|
37
|
-
|
|
44
|
+
selectedChoiceId,
|
|
45
|
+
footer,
|
|
38
46
|
bgColor = colors.bg,
|
|
39
47
|
}: ChoiceDialogProps) {
|
|
40
|
-
const [index, setIndex] = useState(() =>
|
|
48
|
+
const [index, setIndex] = useState(() =>
|
|
49
|
+
clampChoiceIndex(getInitialChoiceIndex(choices, selectedChoiceId), choices.length)
|
|
50
|
+
);
|
|
41
51
|
const selectedIndex = clampChoiceIndex(index, choices.length);
|
|
42
52
|
const selectedChoice = selectedIndex >= 0 ? choices[selectedIndex] : undefined;
|
|
43
53
|
const items = useMemo<ListViewItem[]>(() => choices.map((choice) => ({
|
|
@@ -2,7 +2,7 @@ import { Box, Text, useUiHost } from "../../../ui";
|
|
|
2
2
|
import { TextAttributes } from "../../../ui";
|
|
3
3
|
import { useShortcut } from "../../../react/input";
|
|
4
4
|
import { type AlertContext, useDialog, useDialogKeyboard } from "../../../ui/dialog";
|
|
5
|
-
import { type ReactNode, useEffect, useMemo, useState } from "react";
|
|
5
|
+
import { type ReactNode, useEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import { colors } from "../../../theme/colors";
|
|
7
7
|
import { isDetailBackNavigationKey } from "../../../utils/back-navigation";
|
|
8
8
|
import { isPlainKey } from "../../../utils/keyboard";
|
|
@@ -253,6 +253,7 @@ export function MultiSelectDialogButton({
|
|
|
253
253
|
}: MultiSelectDialogButtonProps) {
|
|
254
254
|
const isDesktopWeb = useUiHost().kind === "desktop-web";
|
|
255
255
|
const dialog = useDialog();
|
|
256
|
+
const triggerMouseDownRef = useRef(false);
|
|
256
257
|
const summary = summarizeMultiSelectValues({ options, selectedValues, emptyLabel });
|
|
257
258
|
const buttonLabel = `${label}: ${summary}`;
|
|
258
259
|
const buttonText = ` ${buttonLabel} `;
|
|
@@ -311,6 +312,17 @@ export function MultiSelectDialogButton({
|
|
|
311
312
|
);
|
|
312
313
|
}
|
|
313
314
|
|
|
315
|
+
const startTriggerPress = (event?: DialogTriggerEvent) => {
|
|
316
|
+
triggerMouseDownRef.current = true;
|
|
317
|
+
stopMouseEvent(event);
|
|
318
|
+
};
|
|
319
|
+
const finishTriggerPress = (event?: DialogTriggerEvent) => {
|
|
320
|
+
const startedOnTrigger = triggerMouseDownRef.current;
|
|
321
|
+
triggerMouseDownRef.current = false;
|
|
322
|
+
if (startedOnTrigger) openDialog(event);
|
|
323
|
+
else stopMouseEvent(event);
|
|
324
|
+
};
|
|
325
|
+
|
|
314
326
|
return (
|
|
315
327
|
<Box
|
|
316
328
|
id={idPrefix ? `${idPrefix}:button` : undefined}
|
|
@@ -318,14 +330,14 @@ export function MultiSelectDialogButton({
|
|
|
318
330
|
width={buttonText.length}
|
|
319
331
|
flexDirection="row"
|
|
320
332
|
backgroundColor={disabled ? colors.panel : colors.selected}
|
|
321
|
-
onMouseDown={
|
|
322
|
-
onMouseUp={
|
|
333
|
+
onMouseDown={startTriggerPress}
|
|
334
|
+
onMouseUp={finishTriggerPress}
|
|
323
335
|
>
|
|
324
336
|
<Text
|
|
325
337
|
fg={disabled ? colors.textMuted : colors.selectedText}
|
|
326
338
|
attributes={TextAttributes.BOLD}
|
|
327
|
-
onMouseDown={
|
|
328
|
-
onMouseUp={
|
|
339
|
+
onMouseDown={startTriggerPress}
|
|
340
|
+
onMouseUp={finishTriggerPress}
|
|
329
341
|
>
|
|
330
342
|
{buttonText}
|
|
331
343
|
</Text>
|