gloomberb 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/patches/@opentui%2Fcore@0.1.90.patch +48 -24
- package/src/app.tsx +9 -6
- package/src/cli/registry.test.ts +0 -1
- package/src/components/chart/chart-controller.ts +0 -1
- package/src/components/chart/chart-data.ts +1 -31
- package/src/components/chart/chart-renderer.ts +70 -0
- package/src/components/chart/chart-resolution.test.ts +0 -36
- package/src/components/chart/chart-resolution.ts +0 -27
- package/src/components/chart/chart.test.ts +17 -45
- package/src/components/chart/comparison-chart-data.test.ts +1 -5
- package/src/components/chart/comparison-chart-data.ts +1 -4
- package/src/components/chart/comparison-stock-chart.tsx +62 -77
- package/src/components/chart/native/kitty.test.ts +23 -0
- package/src/components/chart/native/surface-visibility.ts +48 -0
- package/src/components/chart/static-chart-surface.tsx +28 -4
- package/src/components/chart/stock-chart.test.ts +0 -16
- package/src/components/chart/stock-chart.tsx +52 -71
- package/src/components/chart/time-axis-label.tsx +43 -0
- package/src/components/command-bar/command-bar.test.tsx +71 -8
- package/src/components/command-bar/command-bar.tsx +388 -713
- package/src/components/command-bar/confirm-body.tsx +51 -0
- package/src/components/command-bar/list-model.ts +77 -0
- package/src/components/command-bar/view-model.ts +0 -19
- package/src/components/command-bar/workflow-ops.ts +4 -0
- package/src/components/data-table-stack-view.test.tsx +39 -4
- package/src/components/data-table-stack-view.tsx +11 -104
- package/src/components/data-table-view.tsx +43 -79
- package/src/components/feed-data-table-stack-view.tsx +10 -17
- package/src/components/feed-stack-controller.ts +39 -0
- package/src/components/index.ts +6 -2
- package/src/components/layout/floating-pane.tsx +30 -2
- package/src/components/layout/header.tsx +10 -7
- package/src/components/layout/pane-content.tsx +2 -0
- package/src/components/layout/pane-cycle-order.test.ts +30 -0
- package/src/components/layout/pane-cycle-order.ts +32 -0
- package/src/components/layout/pane-footer.tsx +26 -4
- package/src/components/layout/pane-header.tsx +5 -5
- package/src/components/layout/pane-keyboard-scroll.test.tsx +226 -0
- package/src/components/layout/shell.test.tsx +66 -22
- package/src/components/layout/shell.tsx +13 -6
- package/src/components/onboarding/onboarding-wizard.tsx +1 -2
- package/src/components/pane-settings-dialog.tsx +12 -4
- package/src/components/pane-template-wizard.test.tsx +3 -4
- package/src/components/speedometer-gauge.test.tsx +45 -0
- package/src/components/speedometer-gauge.tsx +558 -0
- package/src/components/table-view-shared.tsx +158 -0
- package/src/components/ticker-list-table-view.tsx +42 -72
- package/src/components/ui/confirm-dialog.tsx +66 -0
- package/src/components/ui/data-table.tsx +18 -10
- package/src/components/ui/index.ts +0 -1
- package/src/components/ui/page-stack-view.tsx +39 -10
- package/src/components/ui/remote-image.tsx +15 -40
- package/src/components/ui/table-layout.test.ts +16 -0
- package/src/components/ui/table-layout.ts +10 -3
- package/src/components/ui/ui.test.tsx +100 -20
- package/src/core/state/app-state.ts +6 -3
- package/src/market-data/coordinator.test.ts +1 -1
- package/src/market-data/coordinator.ts +0 -1
- package/src/news/aggregator.ts +0 -3
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +46 -16
- package/src/plugins/builtin/ai/providers.ts +0 -17
- package/src/plugins/builtin/ai/screener-pane.test.tsx +3 -4
- package/src/plugins/builtin/ai/screener-pane.tsx +0 -3
- package/src/plugins/builtin/ask-ai.test.tsx +89 -2
- package/src/plugins/builtin/broker-manager/index.test.tsx +2 -2
- package/src/plugins/builtin/broker-manager/index.tsx +19 -36
- package/src/plugins/builtin/chat-controller.test.ts +105 -0
- package/src/plugins/builtin/chat-controller.ts +75 -6
- package/src/plugins/builtin/chat.test.tsx +6 -6
- package/src/plugins/builtin/chat.tsx +29 -20
- package/src/plugins/builtin/cloud-tweets.tsx +0 -1
- package/src/plugins/builtin/comparison-chart.test.tsx +53 -2
- package/src/plugins/builtin/comparison-chart.tsx +11 -5
- package/src/plugins/builtin/correlation/index.tsx +1 -6
- package/src/plugins/builtin/debug.tsx +1 -3
- package/src/plugins/builtin/econ/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +72 -0
- package/src/plugins/builtin/fear-greed/fear-greed-data.ts +366 -0
- package/src/plugins/builtin/fear-greed/index.tsx +478 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/help.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +10 -15
- package/src/plugins/builtin/insider/index.tsx +22 -89
- package/src/plugins/builtin/market-movers/index.tsx +1 -1
- package/src/plugins/builtin/news-wire/index.tsx +41 -2
- package/src/plugins/builtin/news-wire/news-table.tsx +11 -11
- package/src/plugins/builtin/news.tsx +4 -16
- package/src/plugins/builtin/notes.tsx +18 -55
- package/src/plugins/builtin/options.test.tsx +58 -2
- package/src/plugins/builtin/options.tsx +108 -49
- package/src/plugins/builtin/plugin-groups.ts +2 -0
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +5 -21
- package/src/plugins/builtin/sec-filing-display.tsx +84 -0
- package/src/plugins/builtin/sec.tsx +13 -66
- package/src/plugins/builtin/sectors/index.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +36 -28
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +69 -4
- package/src/plugins/builtin/ticker-detail.test.tsx +102 -0
- package/src/plugins/builtin/world-indices/index.tsx +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/ibkr/broker-adapter.ts +1 -3
- package/src/plugins/ibkr/gateway-service-native.ts +1 -5
- package/src/plugins/ibkr/gateway-service.ts +1 -1
- package/src/plugins/ibkr/index.test.tsx +0 -2
- package/src/plugins/ibkr/trade-tab.test.tsx +56 -5
- package/src/plugins/ibkr/trade-tab.tsx +13 -11
- package/src/plugins/pane-manager.ts +0 -13
- package/src/plugins/plugin-runtime.test.tsx +6 -6
- package/src/plugins/plugin-runtime.tsx +13 -8
- package/src/plugins/prediction-markets/cli.ts +0 -1
- package/src/plugins/prediction-markets/controller-data.ts +0 -2
- package/src/plugins/prediction-markets/pane.test.tsx +3 -11
- package/src/plugins/prediction-markets/test-helpers.tsx +1 -1
- package/src/plugins/registry.ts +10 -39
- package/src/react/input.ts +11 -2
- package/src/renderers/electrobun/view/data-table.tsx +28 -6
- package/src/renderers/electrobun/view/desktop-controls.tsx +34 -20
- package/src/renderers/electrobun/view/dialog-host.tsx +11 -1
- package/src/renderers/electrobun/view/input-host.tsx +123 -12
- package/src/renderers/electrobun/view/key-event.ts +1 -1
- package/src/renderers/electrobun/view/styles.css +331 -0
- package/src/renderers/electrobun/view/ui-host.tsx +22 -3
- package/src/renderers/opentui/chart-surface.tsx +179 -0
- package/src/renderers/opentui/host.tsx +6 -0
- package/src/renderers/opentui/image-loader.test.ts +25 -0
- package/src/renderers/opentui/image-loader.ts +105 -0
- package/src/renderers/opentui/image-surface.tsx +266 -0
- package/src/renderers/opentui/input-host.tsx +45 -6
- package/src/renderers/opentui/start.tsx +48 -30
- package/src/renderers/opentui/test-utils.tsx +17 -0
- package/src/renderers/opentui/ui-host.tsx +7 -3
- package/src/sources/provider-router.ts +0 -4
- package/src/state/app-context.test.ts +45 -0
- package/src/state/app-context.tsx +4 -0
- package/src/state/pane-scroll-registry.tsx +254 -0
- package/src/state/use-inline-tickers.test.tsx +73 -0
- package/src/state/use-inline-tickers.ts +7 -1
- package/src/test-support/plugin-runtime.ts +3 -2
- package/src/theme/colors.ts +12 -3
- package/src/theme/themes.test.ts +23 -1
- package/src/theme/themes.ts +115 -0
- package/src/types/broker.ts +1 -1
- package/src/types/data-provider.ts +0 -1
- package/src/types/plugin.ts +0 -2
- package/src/ui/host.tsx +4 -0
- package/src/ui/index.tsx +9 -1
- package/src/utils/back-navigation.ts +24 -1
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/feed-pane.tsx +0 -21
- package/src/plugins/builtin/news-wire/top-pane.tsx +0 -21
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from "./test-helpers";
|
|
15
15
|
import { attachPredictionMarketsPersistence } from "./services/fetch";
|
|
16
16
|
import {
|
|
17
|
-
loadKalshiCatalog,
|
|
18
17
|
normalizeKalshiMarket,
|
|
19
18
|
} from "./services/kalshi-adapter";
|
|
20
19
|
import { normalizePolymarketMarket } from "./services/polymarket-adapter";
|
|
@@ -379,7 +378,7 @@ describe("prediction markets pane interactions", () => {
|
|
|
379
378
|
).toBe("polymarket:pm-1");
|
|
380
379
|
});
|
|
381
380
|
|
|
382
|
-
test("supports detail outcome navigation and
|
|
381
|
+
test("supports detail outcome navigation and escape return from the keyboard", async () => {
|
|
383
382
|
attachPredictionMarketsPersistence(new MemoryPersistence());
|
|
384
383
|
|
|
385
384
|
globalThis.fetch = (async (input: Request | string | URL) => {
|
|
@@ -512,20 +511,13 @@ describe("prediction markets pane interactions", () => {
|
|
|
512
511
|
await flushFrames(testSetup);
|
|
513
512
|
|
|
514
513
|
const escapeFrame = testSetup.captureCharFrame();
|
|
515
|
-
expect(escapeFrame).toContain("
|
|
516
|
-
expect(escapeFrame).toContain("Rule 2");
|
|
514
|
+
expect(escapeFrame).toContain("search markets");
|
|
515
|
+
expect(escapeFrame).not.toContain("Rule 2");
|
|
517
516
|
expect(
|
|
518
517
|
harnessStateRef.current?.paneState[TEST_PANE_ID]?.pluginState?.[
|
|
519
518
|
"prediction-markets"
|
|
520
519
|
]?.selectedDetailMarketKey,
|
|
521
520
|
).toBe("kalshi:KXFED-27APR-T4.50");
|
|
522
|
-
|
|
523
|
-
await emitKeypress(testSetup, { name: "backspace", sequence: "\u007f" });
|
|
524
|
-
await flushFrames(testSetup);
|
|
525
|
-
|
|
526
|
-
const frame = testSetup.captureCharFrame();
|
|
527
|
-
expect(frame).toContain("search markets");
|
|
528
|
-
expect(frame).not.toContain("Rule 2");
|
|
529
521
|
});
|
|
530
522
|
|
|
531
523
|
test("opens full-page detail from a row double click and preserves the browse selection", async () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { act, useMemo, useReducer, useRef } from "react";
|
|
2
|
-
import type { ScrollBoxRenderable } from "
|
|
2
|
+
import type { ScrollBoxRenderable } from "@opentui/core";
|
|
3
3
|
import { PaneFooterBar, PaneFooterProvider } from "../../components/layout/pane-footer";
|
|
4
4
|
import {
|
|
5
5
|
AppContext,
|
package/src/plugins/registry.ts
CHANGED
|
@@ -32,9 +32,8 @@ import type {
|
|
|
32
32
|
} from "../types/plugin";
|
|
33
33
|
import type { ContextMenuContext, ContextMenuItem } from "../types/context-menu";
|
|
34
34
|
import type { TickerRecord } from "../types/ticker";
|
|
35
|
-
import { addPaneFloating, removePane } from "./pane-manager";
|
|
36
35
|
import { EventBus, type PluginEvents } from "./event-bus";
|
|
37
|
-
import {
|
|
36
|
+
import { resolvePaneInstance } from "../types/config";
|
|
38
37
|
import { createPluginPersistence } from "./plugin-persistence";
|
|
39
38
|
import { debugLog } from "../utils/debug-log";
|
|
40
39
|
import { PluginRenderProvider, type PluginRuntimeAccess } from "./plugin-runtime";
|
|
@@ -170,9 +169,18 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
170
169
|
openCommandBar = (query?: string) => {
|
|
171
170
|
this.openCommandBarFn(query);
|
|
172
171
|
};
|
|
172
|
+
openPaneSettings = (paneId?: string) => {
|
|
173
|
+
this.openPaneSettingsFn(paneId);
|
|
174
|
+
};
|
|
173
175
|
openPluginCommandWorkflow = (commandId: string) => {
|
|
174
176
|
this.openPluginCommandWorkflowFn(commandId);
|
|
175
177
|
};
|
|
178
|
+
showPane = (paneId: string) => {
|
|
179
|
+
this.showPaneFn(paneId);
|
|
180
|
+
};
|
|
181
|
+
hidePane = (paneId: string) => {
|
|
182
|
+
this.hidePaneFn(paneId);
|
|
183
|
+
};
|
|
176
184
|
|
|
177
185
|
getLayoutFn: (() => LayoutConfig) = () => ({ dockRoot: null, instances: [], floating: [], detached: [] });
|
|
178
186
|
updateLayoutFn: ((layout: LayoutConfig) => void) = () => {};
|
|
@@ -528,41 +536,6 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
528
536
|
}
|
|
529
537
|
}
|
|
530
538
|
|
|
531
|
-
showWidget(paneId: string): void {
|
|
532
|
-
try {
|
|
533
|
-
const disabledPlugins = new Set(this.getConfigFn().disabledPlugins);
|
|
534
|
-
for (const [pluginId, items] of this.pluginItems) {
|
|
535
|
-
if (items.panes.includes(paneId) && disabledPlugins.has(pluginId)) {
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
} catch {
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const layout = this.getLayoutFn();
|
|
544
|
-
const existingInstanceId = this.resolvePaneTarget(paneId);
|
|
545
|
-
if (existingInstanceId && layout.floating.some((entry) => entry.instanceId === existingInstanceId)) {
|
|
546
|
-
this.focusPaneFn(existingInstanceId);
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
const def = this.panesMap.get(paneId);
|
|
551
|
-
const { width, height } = this.getTermSizeFn();
|
|
552
|
-
const instance = existingInstanceId
|
|
553
|
-
? layout.instances.find((entry) => entry.instanceId === existingInstanceId)!
|
|
554
|
-
: createPaneInstance(paneId, { instanceId: `${paneId}:main` });
|
|
555
|
-
const nextLayout = addPaneFloating(layout, instance, width, height, def);
|
|
556
|
-
this.updateLayoutFn(nextLayout);
|
|
557
|
-
this.focusPaneFn(instance.instanceId);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
hideWidget(paneId: string): void {
|
|
561
|
-
const target = this.resolvePaneTarget(paneId);
|
|
562
|
-
if (!target) return;
|
|
563
|
-
this.updateLayoutFn(removePane(this.getLayoutFn(), target));
|
|
564
|
-
}
|
|
565
|
-
|
|
566
539
|
private createContext(pluginId: string): GloomPluginContext {
|
|
567
540
|
const items = this.getOrCreatePluginItems(pluginId);
|
|
568
541
|
|
|
@@ -716,8 +689,6 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
716
689
|
},
|
|
717
690
|
emit: (event, payload) => this.events.emit(event, payload),
|
|
718
691
|
|
|
719
|
-
showWidget: (widgetId) => this.showWidget(widgetId),
|
|
720
|
-
hideWidget: (widgetId) => this.hideWidget(widgetId),
|
|
721
692
|
notify: (notification) => this.notifyFn(notification),
|
|
722
693
|
};
|
|
723
694
|
}
|
package/src/react/input.ts
CHANGED
|
@@ -9,14 +9,23 @@ export interface KeyEventLike {
|
|
|
9
9
|
alt: boolean;
|
|
10
10
|
meta: boolean;
|
|
11
11
|
super?: boolean;
|
|
12
|
+
targetEditable?: boolean;
|
|
13
|
+
readonly defaultPrevented?: boolean;
|
|
14
|
+
readonly propagationStopped?: boolean;
|
|
12
15
|
preventDefault(): void;
|
|
13
16
|
stopPropagation(): void;
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
export interface ShortcutOptions {
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
scope?: string;
|
|
22
|
+
phase?: "normal" | "after";
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
export interface InputHost {
|
|
17
26
|
useShortcut(
|
|
18
27
|
handler: (event: KeyEventLike) => void,
|
|
19
|
-
options?:
|
|
28
|
+
options?: ShortcutOptions,
|
|
20
29
|
): void;
|
|
21
30
|
useViewport(): { width: number; height: number };
|
|
22
31
|
}
|
|
@@ -43,7 +52,7 @@ function useInputHost(): InputHost {
|
|
|
43
52
|
|
|
44
53
|
export function useShortcut(
|
|
45
54
|
handler: (event: KeyEventLike) => void,
|
|
46
|
-
options?:
|
|
55
|
+
options?: ShortcutOptions,
|
|
47
56
|
): void {
|
|
48
57
|
useInputHost().useShortcut(handler, options);
|
|
49
58
|
}
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
DataTableProps,
|
|
25
25
|
DataTableSectionHeader,
|
|
26
26
|
} from "../../../components/ui/data-table";
|
|
27
|
+
import { getTableWidth, hasMeaningfulTableHorizontalOverflow } from "../../../components/ui/table-layout";
|
|
27
28
|
import { WEB_CELL_HEIGHT, WEB_CELL_WIDTH } from "./input-host";
|
|
28
29
|
import { useScrollbarActivity } from "./scrollbar-activity";
|
|
29
30
|
|
|
@@ -489,7 +490,9 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
489
490
|
const headerVertical = useScrollbarState(false);
|
|
490
491
|
const bodyHorizontal = useScrollbarState(showHorizontalScrollbar);
|
|
491
492
|
const bodyVertical = useScrollbarState(true);
|
|
493
|
+
const [viewportWidth, setViewportWidth] = useState(0);
|
|
492
494
|
const [scrollbarActive, markScrollbarActive] = useScrollbarActivity();
|
|
495
|
+
const tableWidth = useMemo(() => getTableWidth(columns), [columns]);
|
|
493
496
|
const gridTemplateColumns = useMemo(
|
|
494
497
|
() => buildGridTemplateColumns(columns),
|
|
495
498
|
[columns],
|
|
@@ -538,6 +541,25 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
538
541
|
const totalHeight = virtualize
|
|
539
542
|
? rowVirtualizer.getTotalSize()
|
|
540
543
|
: WEB_CELL_HEIGHT + items.length * WEB_CELL_HEIGHT;
|
|
544
|
+
const horizontalScrollEnabled = showHorizontalScrollbar
|
|
545
|
+
&& hasMeaningfulTableHorizontalOverflow(tableWidth, viewportWidth);
|
|
546
|
+
const scrollContentWidth = horizontalScrollEnabled
|
|
547
|
+
? Math.max(1, tableWidth * WEB_CELL_WIDTH)
|
|
548
|
+
: "100%";
|
|
549
|
+
const measureViewportWidth = useCallback(() => {
|
|
550
|
+
const element = bodyElementRef.current;
|
|
551
|
+
const nextValue = element ? toCellX(element.clientWidth) : 0;
|
|
552
|
+
setViewportWidth((current) => current === nextValue ? current : nextValue);
|
|
553
|
+
}, []);
|
|
554
|
+
|
|
555
|
+
useEffect(() => {
|
|
556
|
+
measureViewportWidth();
|
|
557
|
+
const element = bodyElementRef.current;
|
|
558
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
559
|
+
const observer = new ResizeObserver(measureViewportWidth);
|
|
560
|
+
observer.observe(element);
|
|
561
|
+
return () => observer.disconnect();
|
|
562
|
+
}, [measureViewportWidth]);
|
|
541
563
|
|
|
542
564
|
useEffect(() => {
|
|
543
565
|
if (scrollToIndex == null || items.length === 0) return;
|
|
@@ -589,11 +611,11 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
589
611
|
|
|
590
612
|
useEffect(() => {
|
|
591
613
|
headerHorizontal.bar.visible = false;
|
|
592
|
-
bodyHorizontal.bar.visible =
|
|
593
|
-
if (!
|
|
614
|
+
bodyHorizontal.bar.visible = horizontalScrollEnabled;
|
|
615
|
+
if (!horizontalScrollEnabled) {
|
|
594
616
|
if (bodyElementRef.current) bodyElementRef.current.scrollLeft = 0;
|
|
595
617
|
}
|
|
596
|
-
}, [bodyHorizontal.bar, headerHorizontal.bar,
|
|
618
|
+
}, [bodyHorizontal.bar, headerHorizontal.bar, horizontalScrollEnabled]);
|
|
597
619
|
|
|
598
620
|
const rootStyle: CSSProperties = {
|
|
599
621
|
display: "flex",
|
|
@@ -610,7 +632,7 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
610
632
|
flex: "1 1 0px",
|
|
611
633
|
minWidth: 0,
|
|
612
634
|
minHeight: 0,
|
|
613
|
-
overflowX:
|
|
635
|
+
overflowX: horizontalScrollEnabled ? "auto" : "hidden",
|
|
614
636
|
overflowY: "auto",
|
|
615
637
|
backgroundColor: colors.bg,
|
|
616
638
|
};
|
|
@@ -621,7 +643,7 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
621
643
|
ref={bodyElementRef}
|
|
622
644
|
data-gloom-role="data-table-body-scroll"
|
|
623
645
|
data-gloom-scrollbar-x={
|
|
624
|
-
|
|
646
|
+
horizontalScrollEnabled && bodyHorizontal.visible
|
|
625
647
|
? "visible"
|
|
626
648
|
: "hidden"
|
|
627
649
|
}
|
|
@@ -646,7 +668,7 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
646
668
|
data-gloom-role="data-table-scroll-content"
|
|
647
669
|
style={{
|
|
648
670
|
position: "relative",
|
|
649
|
-
width:
|
|
671
|
+
width: scrollContentWidth,
|
|
650
672
|
height: items.length > 0 ? totalHeight : "100%",
|
|
651
673
|
minHeight: WEB_CELL_HEIGHT,
|
|
652
674
|
}}
|
|
@@ -14,13 +14,27 @@ import type { PageStackViewProps } from "../../../components/ui/page-stack-view"
|
|
|
14
14
|
import type { SegmentedControlProps } from "../../../components/ui/toggle";
|
|
15
15
|
|
|
16
16
|
const CONTROL_RADIUS = 6;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
|
|
18
|
+
function panelBorder(): string {
|
|
19
|
+
return blendHex(colors.border, colors.borderFocused, 0.18);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function panelFill(): string {
|
|
23
|
+
return blendHex(colors.panel, colors.bg, 0.22);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function subtlePanelFill(): string {
|
|
27
|
+
return blendHex(colors.panel, colors.bg, 0.42);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function selectedPanelFill(): string {
|
|
31
|
+
return blendHex(colors.selected, colors.bg, 0.42);
|
|
32
|
+
}
|
|
19
33
|
|
|
20
34
|
function controlBorderColor(focused = false, active = false): string {
|
|
21
35
|
if (active) return colors.borderFocused;
|
|
22
36
|
if (focused) return blendHex(colors.borderFocused, colors.textBright, 0.24);
|
|
23
|
-
return
|
|
37
|
+
return panelBorder();
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
function controlShadow(active = false): string {
|
|
@@ -33,8 +47,8 @@ function buttonPalette(props: Pick<ButtonProps, "variant" | "active" | "disabled
|
|
|
33
47
|
if (props.disabled) {
|
|
34
48
|
return {
|
|
35
49
|
fg: colors.textMuted,
|
|
36
|
-
bg:
|
|
37
|
-
border:
|
|
50
|
+
bg: subtlePanelFill(),
|
|
51
|
+
border: panelBorder(),
|
|
38
52
|
};
|
|
39
53
|
}
|
|
40
54
|
if (props.active) {
|
|
@@ -62,14 +76,14 @@ function buttonPalette(props: Pick<ButtonProps, "variant" | "active" | "disabled
|
|
|
62
76
|
return {
|
|
63
77
|
fg: colors.textDim,
|
|
64
78
|
bg: "rgba(0, 0, 0, 0)",
|
|
65
|
-
border:
|
|
79
|
+
border: panelBorder(),
|
|
66
80
|
};
|
|
67
81
|
case "secondary":
|
|
68
82
|
default:
|
|
69
83
|
return {
|
|
70
84
|
fg: colors.text,
|
|
71
|
-
bg:
|
|
72
|
-
border:
|
|
85
|
+
bg: panelFill(),
|
|
86
|
+
border: panelBorder(),
|
|
73
87
|
};
|
|
74
88
|
}
|
|
75
89
|
}
|
|
@@ -248,7 +262,7 @@ export function WebMessageComposer({
|
|
|
248
262
|
flexDirection="row"
|
|
249
263
|
width={width}
|
|
250
264
|
height={height}
|
|
251
|
-
backgroundColor={
|
|
265
|
+
backgroundColor={panelFill()}
|
|
252
266
|
onMouseDown={requestFocus}
|
|
253
267
|
data-gloom-role="desktop-message-composer"
|
|
254
268
|
style={{
|
|
@@ -350,7 +364,7 @@ export function WebListView({
|
|
|
350
364
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
351
365
|
const scrollRef = useRef<ScrollBoxRenderable>(null);
|
|
352
366
|
const baseBg = bgColor ?? colors.bg;
|
|
353
|
-
const activeBg = selectedBgColor ??
|
|
367
|
+
const activeBg = selectedBgColor ?? selectedPanelFill();
|
|
354
368
|
const rowHoverBg = hoverBgColor ?? hoverBg();
|
|
355
369
|
const selectedItem = selectedIndex >= 0 ? items[selectedIndex] : undefined;
|
|
356
370
|
const activeScrollIndex = scrollIndex ?? selectedIndex;
|
|
@@ -435,10 +449,10 @@ export function WebListView({
|
|
|
435
449
|
backgroundColor: "transparent",
|
|
436
450
|
}
|
|
437
451
|
: {
|
|
438
|
-
border: `1px solid ${
|
|
452
|
+
border: `1px solid ${panelBorder()}`,
|
|
439
453
|
borderRadius: CONTROL_RADIUS,
|
|
440
454
|
padding: 4,
|
|
441
|
-
backgroundColor:
|
|
455
|
+
backgroundColor: subtlePanelFill(),
|
|
442
456
|
}}
|
|
443
457
|
>
|
|
444
458
|
<Box flexDirection="column" gap={rowGap}>
|
|
@@ -454,9 +468,9 @@ export function WebListView({
|
|
|
454
468
|
{showSelectedDescription && selectedItem?.description && (
|
|
455
469
|
<Box
|
|
456
470
|
flexDirection="row"
|
|
457
|
-
backgroundColor=
|
|
471
|
+
backgroundColor={subtlePanelFill()}
|
|
458
472
|
style={{
|
|
459
|
-
border: `1px solid ${
|
|
473
|
+
border: `1px solid ${panelBorder()}`,
|
|
460
474
|
borderRadius: CONTROL_RADIUS,
|
|
461
475
|
paddingInline: 10,
|
|
462
476
|
}}
|
|
@@ -478,9 +492,9 @@ export function WebSegmentedControl({
|
|
|
478
492
|
return (
|
|
479
493
|
<Box
|
|
480
494
|
flexDirection="row"
|
|
481
|
-
backgroundColor=
|
|
495
|
+
backgroundColor={panelFill()}
|
|
482
496
|
style={{
|
|
483
|
-
border: `1px solid ${
|
|
497
|
+
border: `1px solid ${panelBorder()}`,
|
|
484
498
|
borderRadius: CONTROL_RADIUS,
|
|
485
499
|
padding: 2,
|
|
486
500
|
}}
|
|
@@ -531,7 +545,7 @@ export function WebDialogFrame({
|
|
|
531
545
|
flexDirection="row"
|
|
532
546
|
alignItems="center"
|
|
533
547
|
style={{
|
|
534
|
-
borderBottom: showTitleDivider ? `1px solid ${
|
|
548
|
+
borderBottom: showTitleDivider ? `1px solid ${panelBorder()}` : "none",
|
|
535
549
|
paddingBottom: showTitleDivider ? 8 : 0,
|
|
536
550
|
marginBottom: showTitleDivider ? 10 : 14,
|
|
537
551
|
}}
|
|
@@ -545,7 +559,7 @@ export function WebDialogFrame({
|
|
|
545
559
|
<Box
|
|
546
560
|
height={1}
|
|
547
561
|
style={{
|
|
548
|
-
borderTop: `1px solid ${
|
|
562
|
+
borderTop: `1px solid ${panelBorder()}`,
|
|
549
563
|
paddingTop: 8,
|
|
550
564
|
marginTop: 10,
|
|
551
565
|
}}
|
|
@@ -600,7 +614,7 @@ export function WebPageStackView({
|
|
|
600
614
|
height={1}
|
|
601
615
|
flexDirection="row"
|
|
602
616
|
alignItems="center"
|
|
603
|
-
backgroundColor={
|
|
617
|
+
backgroundColor={panelFill()}
|
|
604
618
|
onMouseDown={(event) => {
|
|
605
619
|
event.preventDefault?.();
|
|
606
620
|
event.stopPropagation?.();
|
|
@@ -608,7 +622,7 @@ export function WebPageStackView({
|
|
|
608
622
|
}}
|
|
609
623
|
data-gloom-interactive="true"
|
|
610
624
|
style={{
|
|
611
|
-
border: `1px solid ${
|
|
625
|
+
border: `1px solid ${panelBorder()}`,
|
|
612
626
|
borderRadius: CONTROL_RADIUS,
|
|
613
627
|
paddingInline: 8,
|
|
614
628
|
cursor: "pointer",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/** @jsxImportSource react */
|
|
3
3
|
import { useCallback, useMemo, useState, type ReactNode } from "react";
|
|
4
4
|
import { DialogHostProvider, type DialogApi } from "../../../ui/dialog";
|
|
5
|
+
import { blendHex, colors } from "../../../theme/colors";
|
|
5
6
|
|
|
6
7
|
interface DialogState {
|
|
7
8
|
id: string;
|
|
@@ -13,6 +14,8 @@ let nextDialogId = 1;
|
|
|
13
14
|
|
|
14
15
|
export function WebDialogHostProvider({ children }: { children: ReactNode }) {
|
|
15
16
|
const [dialogState, setDialogState] = useState<DialogState | null>(null);
|
|
17
|
+
const dialogBorder = blendHex(colors.border, colors.borderFocused, 0.18);
|
|
18
|
+
const dialogBg = blendHex(colors.panel, colors.bg, 0.12);
|
|
16
19
|
|
|
17
20
|
const close = useCallback((value?: unknown) => {
|
|
18
21
|
setDialogState((current) => {
|
|
@@ -46,7 +49,14 @@ export function WebDialogHostProvider({ children }: { children: ReactNode }) {
|
|
|
46
49
|
if (event.target === event.currentTarget) close(undefined);
|
|
47
50
|
}}
|
|
48
51
|
>
|
|
49
|
-
<div
|
|
52
|
+
<div
|
|
53
|
+
className="gloom-dialog"
|
|
54
|
+
style={{
|
|
55
|
+
borderColor: dialogBorder,
|
|
56
|
+
background: dialogBg,
|
|
57
|
+
color: colors.text,
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
50
60
|
{typeof dialogState.content === "function"
|
|
51
61
|
? dialogState.content({
|
|
52
62
|
dialogId: dialogState.id,
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/** @jsxImportSource react */
|
|
3
|
-
import { useEffect, useMemo, useSyncExternalStore, type ReactNode } from "react";
|
|
4
|
-
import { InputHostProvider, type InputHost, type KeyEventLike } from "../../../react/input";
|
|
5
|
-
import {
|
|
3
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore, type ReactNode } from "react";
|
|
4
|
+
import { InputHostProvider, type InputHost, type KeyEventLike, type ShortcutOptions } from "../../../react/input";
|
|
5
|
+
import {
|
|
6
|
+
isMouseBackNavigationButton,
|
|
7
|
+
MOUSE_BACK_NAVIGATION_EVENT_NAME,
|
|
8
|
+
} from "../../../utils/back-navigation";
|
|
9
|
+
import {
|
|
10
|
+
hasWebCtrlModifier,
|
|
11
|
+
isEditableKeyboardTarget,
|
|
12
|
+
normalizeWebKeyName,
|
|
13
|
+
shouldConsumeWebAppKeyDown,
|
|
14
|
+
webKeySequence,
|
|
15
|
+
} from "./key-event";
|
|
6
16
|
|
|
7
17
|
export const WEB_CELL_WIDTH = 8;
|
|
8
18
|
export const WEB_CELL_HEIGHT = 18;
|
|
9
19
|
|
|
10
20
|
function toKeyEventLike(event: KeyboardEvent): KeyEventLike {
|
|
11
21
|
const key = normalizeWebKeyName(event.key);
|
|
22
|
+
let propagationStopped = false;
|
|
12
23
|
return {
|
|
13
24
|
key,
|
|
14
25
|
name: key,
|
|
@@ -18,11 +29,56 @@ function toKeyEventLike(event: KeyboardEvent): KeyEventLike {
|
|
|
18
29
|
alt: event.altKey,
|
|
19
30
|
meta: event.metaKey,
|
|
20
31
|
super: event.metaKey,
|
|
32
|
+
targetEditable: isEditableKeyboardTarget(event.target),
|
|
33
|
+
get defaultPrevented() {
|
|
34
|
+
return event.defaultPrevented;
|
|
35
|
+
},
|
|
36
|
+
get propagationStopped() {
|
|
37
|
+
return propagationStopped;
|
|
38
|
+
},
|
|
39
|
+
preventDefault: () => event.preventDefault(),
|
|
40
|
+
stopPropagation: () => {
|
|
41
|
+
propagationStopped = true;
|
|
42
|
+
event.stopPropagation();
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function toMouseBackKeyEventLike(event: MouseEvent): KeyEventLike {
|
|
48
|
+
let propagationStopped = false;
|
|
49
|
+
return {
|
|
50
|
+
key: MOUSE_BACK_NAVIGATION_EVENT_NAME,
|
|
51
|
+
name: MOUSE_BACK_NAVIGATION_EVENT_NAME,
|
|
52
|
+
sequence: "",
|
|
53
|
+
ctrl: event.ctrlKey,
|
|
54
|
+
shift: event.shiftKey,
|
|
55
|
+
alt: event.altKey,
|
|
56
|
+
meta: event.metaKey,
|
|
57
|
+
super: event.metaKey,
|
|
58
|
+
targetEditable: isEditableKeyboardTarget(event.target),
|
|
59
|
+
get defaultPrevented() {
|
|
60
|
+
return event.defaultPrevented;
|
|
61
|
+
},
|
|
62
|
+
get propagationStopped() {
|
|
63
|
+
return propagationStopped;
|
|
64
|
+
},
|
|
21
65
|
preventDefault: () => event.preventDefault(),
|
|
22
|
-
stopPropagation: () =>
|
|
66
|
+
stopPropagation: () => {
|
|
67
|
+
propagationStopped = true;
|
|
68
|
+
event.stopPropagation();
|
|
69
|
+
},
|
|
23
70
|
};
|
|
24
71
|
}
|
|
25
72
|
|
|
73
|
+
interface ShortcutEntry {
|
|
74
|
+
handlerRef: { current: (event: KeyEventLike) => void };
|
|
75
|
+
enabledRef: { current: boolean };
|
|
76
|
+
phase: NonNullable<ShortcutOptions["phase"]>;
|
|
77
|
+
order: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let nextShortcutOrder = 1;
|
|
81
|
+
|
|
26
82
|
function subscribeViewport(listener: () => void): () => void {
|
|
27
83
|
window.addEventListener("resize", listener);
|
|
28
84
|
return () => window.removeEventListener("resize", listener);
|
|
@@ -40,17 +96,72 @@ function getViewport() {
|
|
|
40
96
|
}
|
|
41
97
|
|
|
42
98
|
export function WebInputHostProvider({ children }: { children: ReactNode }) {
|
|
99
|
+
const shortcutsRef = useRef<ShortcutEntry[]>([]);
|
|
100
|
+
|
|
101
|
+
const dispatchShortcut = (shortcutEvent: KeyEventLike) => {
|
|
102
|
+
for (const phase of ["normal", "after"] as const) {
|
|
103
|
+
if (phase === "after" && (shortcutEvent.defaultPrevented || shortcutEvent.propagationStopped)) break;
|
|
104
|
+
for (const entry of shortcutsRef.current) {
|
|
105
|
+
if (entry.phase !== phase || !entry.enabledRef.current) continue;
|
|
106
|
+
entry.handlerRef.current(shortcutEvent);
|
|
107
|
+
if (shortcutEvent.propagationStopped) break;
|
|
108
|
+
}
|
|
109
|
+
if (shortcutEvent.propagationStopped) break;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
115
|
+
const shortcutEvent = toKeyEventLike(event);
|
|
116
|
+
dispatchShortcut(shortcutEvent);
|
|
117
|
+
if (shouldConsumeWebAppKeyDown(event)) event.preventDefault();
|
|
118
|
+
};
|
|
119
|
+
window.addEventListener("keydown", onKeyDown);
|
|
120
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
const preventBrowserBack = (event: MouseEvent) => {
|
|
125
|
+
if (!isMouseBackNavigationButton(event.button)) return;
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
event.stopPropagation();
|
|
128
|
+
};
|
|
129
|
+
const onMouseUp = (event: MouseEvent) => {
|
|
130
|
+
if (!isMouseBackNavigationButton(event.button)) return;
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
event.stopPropagation();
|
|
133
|
+
dispatchShortcut(toMouseBackKeyEventLike(event));
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
window.addEventListener("mousedown", preventBrowserBack, true);
|
|
137
|
+
window.addEventListener("auxclick", preventBrowserBack, true);
|
|
138
|
+
window.addEventListener("mouseup", onMouseUp, true);
|
|
139
|
+
return () => {
|
|
140
|
+
window.removeEventListener("mousedown", preventBrowserBack, true);
|
|
141
|
+
window.removeEventListener("auxclick", preventBrowserBack, true);
|
|
142
|
+
window.removeEventListener("mouseup", onMouseUp, true);
|
|
143
|
+
};
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
43
146
|
const host = useMemo<InputHost>(() => ({
|
|
44
147
|
useShortcut(handler, options) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
148
|
+
const handlerRef = useRef(handler);
|
|
149
|
+
const enabledRef = useRef(options?.enabled !== false);
|
|
150
|
+
handlerRef.current = handler;
|
|
151
|
+
enabledRef.current = options?.enabled !== false;
|
|
152
|
+
|
|
153
|
+
useLayoutEffect(() => {
|
|
154
|
+
const entry: ShortcutEntry = {
|
|
155
|
+
handlerRef,
|
|
156
|
+
enabledRef,
|
|
157
|
+
phase: options?.phase ?? "normal",
|
|
158
|
+
order: nextShortcutOrder++,
|
|
159
|
+
};
|
|
160
|
+
shortcutsRef.current = [...shortcutsRef.current, entry].sort((a, b) => a.order - b.order);
|
|
161
|
+
return () => {
|
|
162
|
+
shortcutsRef.current = shortcutsRef.current.filter((current) => current !== entry);
|
|
50
163
|
};
|
|
51
|
-
|
|
52
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
53
|
-
}, [handler, options?.enabled, options?.scope]);
|
|
164
|
+
}, [options?.phase, options?.scope]);
|
|
54
165
|
},
|
|
55
166
|
useViewport() {
|
|
56
167
|
return useSyncExternalStore(subscribeViewport, getViewport, getViewport);
|
|
@@ -38,7 +38,7 @@ function targetHasClosest(target: KeyboardTargetLike, selector: string): boolean
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function isEditableKeyboardTarget(target: EventTarget | null): boolean {
|
|
41
|
+
export function isEditableKeyboardTarget(target: EventTarget | null): boolean {
|
|
42
42
|
const element = getKeyboardTarget(target);
|
|
43
43
|
if (!element) return false;
|
|
44
44
|
|