gloomberb 0.7.1 → 0.7.3
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 +79 -132
- package/package.json +1 -1
- package/src/app.tsx +73 -67
- package/src/components/chart/chart-data.ts +7 -2
- package/src/components/chart/chart-pane-settings.ts +7 -4
- package/src/components/chart/chart-renderer.ts +49 -15
- package/src/components/chart/chart-types.ts +2 -2
- package/src/components/chart/chart.test.ts +27 -8
- package/src/components/chart/comparison-stock-chart.tsx +84 -121
- package/src/components/chart/native/chart-rasterizer.ts +43 -17
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +1 -1
- package/src/components/chart/stock-chart.tsx +59 -41
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +528 -383
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +1 -0
- package/src/components/command-bar/view-model.ts +2 -1
- package/src/components/command-bar/workflow-ops.ts +11 -32
- package/src/components/data-table-view.test.tsx +24 -1
- package/src/components/data-table-view.tsx +3 -2
- package/src/components/feed-data-table-stack-view.tsx +3 -2
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/pane-footer.tsx +1 -1
- package/src/components/layout/pane-header.tsx +4 -2
- package/src/components/layout/pane-sizing.ts +12 -0
- package/src/components/layout/pane.tsx +24 -2
- package/src/components/layout/shell.tsx +24 -10
- package/src/components/layout/status-bar.test.tsx +4 -1
- package/src/components/layout/status-bar.tsx +4 -24
- package/src/components/markdown-text.tsx +53 -8
- package/src/components/onboarding/onboarding-wizard.tsx +7 -6
- package/src/components/pane-settings-dialog.tsx +9 -6
- package/src/components/pane-template-wizard.tsx +3 -2
- package/src/components/price-selector-dialog.tsx +3 -2
- package/src/components/price-sparkline-view.tsx +463 -0
- package/src/components/price-sparkline.ts +46 -0
- package/src/components/table-view-shared.tsx +47 -7
- package/src/components/ticker-list-table-view.tsx +3 -2
- package/src/components/ticker-list-table.tsx +11 -51
- package/src/components/toggle-list.tsx +3 -3
- package/src/components/ui/choice-dialog.tsx +3 -2
- package/src/components/ui/multi-select-dialog.tsx +3 -2
- package/src/components/ui/native-select.tsx +3 -3
- package/src/core/state/app-state.ts +210 -80
- package/src/data/config-store-node.ts +113 -25
- package/src/data/config-store.test.ts +65 -3
- package/src/data/plugin-state-store.ts +39 -13
- package/src/data/sqlite-cache.test.ts +13 -0
- package/src/market-data/coordinator.ts +8 -1
- package/src/news/aggregator.test.ts +41 -0
- package/src/news/aggregator.ts +61 -9
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
- package/src/plugins/builtin/ai/settings.ts +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +4 -3
- package/src/plugins/builtin/chat-controller.test.ts +398 -17
- package/src/plugins/builtin/chat-controller.ts +258 -44
- package/src/plugins/builtin/chat.test.tsx +257 -6
- package/src/plugins/builtin/chat.tsx +225 -60
- package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
- package/src/plugins/builtin/correlation/colors.test.ts +39 -0
- package/src/plugins/builtin/correlation/colors.ts +87 -0
- package/src/plugins/builtin/correlation/index.test.tsx +151 -0
- package/src/plugins/builtin/correlation/index.tsx +92 -21
- package/src/plugins/builtin/debug.tsx +4 -3
- package/src/plugins/builtin/econ/index.tsx +3 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +25 -13
- package/src/plugins/builtin/layout-manager.tsx +1 -7
- package/src/plugins/builtin/news-wire/index.tsx +1 -1
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
- package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
- package/src/plugins/builtin/options.tsx +9 -17
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
- package/src/plugins/builtin/portfolio-list/metrics.ts +11 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +117 -18
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
- package/src/plugins/builtin/portfolio-list.test.tsx +282 -2
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +252 -41
- package/src/plugins/builtin/sectors/sector-data.ts +41 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +0 -6
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +442 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- package/src/plugins/ibkr/trading-pane.tsx +3 -2
- package/src/plugins/plugin-runtime.tsx +2 -2
- package/src/plugins/prediction-markets/settings.ts +4 -0
- package/src/plugins/registry.test.ts +55 -0
- package/src/plugins/registry.ts +11 -4
- package/src/plugins/ticker-navigation.test.ts +35 -5
- package/src/plugins/ticker-navigation.ts +16 -22
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
- package/src/renderers/electrobun/bun/application-menu.ts +6 -1
- package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
- package/src/renderers/electrobun/bun/index.ts +52 -13
- package/src/renderers/electrobun/view/app-services.ts +51 -9
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
- package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
- package/src/renderers/electrobun/view/main.tsx +0 -16
- package/src/renderers/electrobun/view/styles.css +23 -21
- package/src/renderers/electrobun/view/ui-host.tsx +67 -11
- package/src/renderers/opentui/host.tsx +2 -1
- package/src/renderers/opentui/start.tsx +1 -1
- package/src/renderers/opentui/test-utils.tsx +6 -5
- package/src/state/app-bootstrap.ts +3 -3
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/ui/host.tsx +8 -2
- package/src/ui/index.tsx +1 -0
- package/src/utils/api-client.test.ts +128 -0
- package/src/utils/api-client.ts +112 -7
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
|
@@ -10,16 +10,15 @@ import {
|
|
|
10
10
|
import { createAppServices, type AppServices } from "../../../core/app-services";
|
|
11
11
|
import { getDataDir, initDataDir, saveConfig, resetAllData, exportConfig, importConfig, setConfigStoreHost } from "../../../data/config-store";
|
|
12
12
|
import * as nodeConfigStoreHost from "../../../data/config-store-node";
|
|
13
|
-
import {
|
|
13
|
+
import { findPaneInstance, type AppConfig } from "../../../types/config";
|
|
14
14
|
import type { AppSessionSnapshot } from "../../../core/state/session-persistence";
|
|
15
|
-
import type
|
|
15
|
+
import { syncConfigActiveLayoutState, type PaneRuntimeState } from "../../../core/state/app-state";
|
|
16
16
|
import type { DesktopDockPreviewState, DesktopSharedStateSnapshot, DesktopThemePreviewState } from "../../../types/desktop-window";
|
|
17
17
|
import type { ReleaseInfo, UpdateCheckResult, UpdateProgress } from "../../../updater";
|
|
18
18
|
import { buildSoundCommand } from "../../../notifications/app-notifier";
|
|
19
19
|
import { isPaneDetached } from "../../../plugins/pane-manager";
|
|
20
20
|
import { ELECTROBUN_CONTEXT_MENU_ACTION, type ElectrobunDesktopRpcSchema } from "../shared/protocol";
|
|
21
21
|
import { decodeRpcValue, encodeRpcValue } from "../view/rpc-codec";
|
|
22
|
-
import { startMainThreadMonitor } from "../../../utils/main-thread-monitor";
|
|
23
22
|
import { contextMenuSelectionMessage } from "./context-menu-click";
|
|
24
23
|
import { getContextMenuRequestId, normalizeContextMenuItems } from "./context-menu-normalize";
|
|
25
24
|
import { createDesktopWorkspace, type DesktopWorkspace } from "./desktop-workspace";
|
|
@@ -56,7 +55,6 @@ console.log = (...args) => console.error(...args);
|
|
|
56
55
|
console.info = (...args) => console.error(...args);
|
|
57
56
|
console.warn = (...args) => console.error(...args);
|
|
58
57
|
|
|
59
|
-
startMainThreadMonitor("electrobun.bun", { mirrorToConsole: true });
|
|
60
58
|
setConfigStoreHost(nodeConfigStoreHost);
|
|
61
59
|
setNativeIbkrGatewayModuleLoader(() => import("../../../plugins/ibkr/gateway-service-native"));
|
|
62
60
|
|
|
@@ -136,17 +134,21 @@ function forEachReadyWindowRpc(callback: (rpc: DesktopRpc) => void): void {
|
|
|
136
134
|
}
|
|
137
135
|
}
|
|
138
136
|
|
|
137
|
+
function openMainWindowDevTools(): void {
|
|
138
|
+
mainWindow?.webview.openDevTools();
|
|
139
|
+
}
|
|
140
|
+
|
|
139
141
|
function scopeClientId(rpc: DesktopRpc, id: string): string {
|
|
140
142
|
return `${getRpcWindowKey(rpc) ?? "window"}:${id}`;
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
function syncActiveLayout(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
function syncActiveLayout(
|
|
146
|
+
config: AppConfig,
|
|
147
|
+
paneState: Record<string, PaneRuntimeState> = config.layouts[config.activeLayoutIndex]?.paneState ?? {},
|
|
148
|
+
focusedPaneId: string | null = config.layouts[config.activeLayoutIndex]?.focusedPaneId ?? null,
|
|
149
|
+
activePanel: "left" | "right" = config.layouts[config.activeLayoutIndex]?.activePanel ?? "left",
|
|
150
|
+
): AppConfig {
|
|
151
|
+
return syncConfigActiveLayoutState(config, paneState, focusedPaneId, activePanel);
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
function normalizeInitWindowTarget(
|
|
@@ -416,6 +418,25 @@ async function runDesktopUpdate(rpc: DesktopRpc, currentVersion: string): Promis
|
|
|
416
418
|
}
|
|
417
419
|
}
|
|
418
420
|
|
|
421
|
+
interface PluginStateBackendSetEntry {
|
|
422
|
+
pluginId: string;
|
|
423
|
+
key: string;
|
|
424
|
+
value: unknown;
|
|
425
|
+
schemaVersion?: number;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function normalizePluginStateSetEntry(entry: unknown): PluginStateBackendSetEntry | null {
|
|
429
|
+
if (!entry || typeof entry !== "object") return null;
|
|
430
|
+
const record = entry as Record<string, unknown>;
|
|
431
|
+
if (typeof record.pluginId !== "string" || typeof record.key !== "string") return null;
|
|
432
|
+
return {
|
|
433
|
+
pluginId: record.pluginId,
|
|
434
|
+
key: record.key,
|
|
435
|
+
value: record.value,
|
|
436
|
+
schemaVersion: typeof record.schemaVersion === "number" ? record.schemaVersion : undefined,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
419
440
|
function syncBackendCloudAuthState(pluginId: string, key: string, value: unknown): void {
|
|
420
441
|
if (pluginId !== "gloomberb-cloud" || (key !== "session" && key !== "resume:session")) return;
|
|
421
442
|
|
|
@@ -670,7 +691,7 @@ async function commitDesktopSnapshot(
|
|
|
670
691
|
snapshot: DesktopSharedStateSnapshot,
|
|
671
692
|
options: { persistConfig?: boolean; reconcileWindows?: boolean } = {},
|
|
672
693
|
): Promise<DesktopSharedStateSnapshot> {
|
|
673
|
-
const nextConfig = syncActiveLayout(snapshot.config);
|
|
694
|
+
const nextConfig = syncActiveLayout(snapshot.config, snapshot.paneState, snapshot.focusedPaneId, snapshot.activePanel);
|
|
674
695
|
setCurrentConfig(nextConfig);
|
|
675
696
|
desktopWorkspace = requireDesktopWorkspace();
|
|
676
697
|
desktopWorkspace.replaceConfig(nextConfig, { layoutChanged: snapshot.layoutChanged });
|
|
@@ -1049,6 +1070,19 @@ async function handleBackendRequest(
|
|
|
1049
1070
|
requireServices().persistence.pluginState.set(payload.pluginId as string, payload.key as string, payload.value, payload.schemaVersion as number | undefined);
|
|
1050
1071
|
syncBackendCloudAuthState(payload.pluginId as string, payload.key as string, payload.value);
|
|
1051
1072
|
return null;
|
|
1073
|
+
case "pluginState.setMany": {
|
|
1074
|
+
const entries = Array.isArray(payload.entries)
|
|
1075
|
+
? payload.entries.flatMap((entry) => {
|
|
1076
|
+
const normalized = normalizePluginStateSetEntry(entry);
|
|
1077
|
+
return normalized ? [normalized] : [];
|
|
1078
|
+
})
|
|
1079
|
+
: [];
|
|
1080
|
+
requireServices().persistence.pluginState.setMany(entries);
|
|
1081
|
+
for (const entry of entries) {
|
|
1082
|
+
syncBackendCloudAuthState(entry.pluginId, entry.key, entry.value);
|
|
1083
|
+
}
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1052
1086
|
case "pluginState.delete":
|
|
1053
1087
|
requireServices().persistence.pluginState.delete(payload.pluginId as string, payload.key as string);
|
|
1054
1088
|
syncBackendCloudAuthState(payload.pluginId as string, payload.key as string, null);
|
|
@@ -1142,7 +1176,12 @@ Electrobun.events.on("context-menu-clicked", (event: unknown) => {
|
|
|
1142
1176
|
|
|
1143
1177
|
ApplicationMenu.on("application-menu-clicked", (event: unknown) => {
|
|
1144
1178
|
const command = applicationMenuCommand(event);
|
|
1145
|
-
if (!command
|
|
1179
|
+
if (!command) return;
|
|
1180
|
+
if (command.type === "open-devtools") {
|
|
1181
|
+
openMainWindowDevTools();
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (!readyWindowRpcs.has(MAIN_WINDOW_RPC_KEY)) return;
|
|
1146
1185
|
windowRpcs.get(MAIN_WINDOW_RPC_KEY)?.send["application-menu.select"]({ command });
|
|
1147
1186
|
});
|
|
1148
1187
|
|
|
@@ -24,6 +24,7 @@ import { getRendererBuiltinPlugins } from "../../../plugins/catalog-ui";
|
|
|
24
24
|
const servicesLog = debugLog.createLogger("services");
|
|
25
25
|
const ASSET_DATA_CAPABILITY_ID = "asset-data.asset-data-router";
|
|
26
26
|
const NEWS_CAPABILITY_ID = "news.core";
|
|
27
|
+
const PLUGIN_STATE_BACKEND_FLUSH_DELAY_MS = 25;
|
|
27
28
|
|
|
28
29
|
class RemoteTickerRepository {
|
|
29
30
|
async loadAllTickers(): Promise<TickerRecord[]> {
|
|
@@ -274,14 +275,19 @@ class RemoteSessionStore {
|
|
|
274
275
|
}
|
|
275
276
|
}
|
|
276
277
|
|
|
278
|
+
interface PluginStatePersistEntry {
|
|
279
|
+
pluginId: string;
|
|
280
|
+
key: string;
|
|
281
|
+
value: unknown;
|
|
282
|
+
schemaVersion: number;
|
|
283
|
+
}
|
|
284
|
+
|
|
277
285
|
class RemotePluginStateStore {
|
|
278
286
|
private readonly state = new Map<string, Map<string, unknown>>();
|
|
279
|
-
private readonly schedulers = new Map<string, ReturnType<typeof createPersistScheduler<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
schemaVersion: number;
|
|
284
|
-
}>>>();
|
|
287
|
+
private readonly schedulers = new Map<string, ReturnType<typeof createPersistScheduler<PluginStatePersistEntry>>>();
|
|
288
|
+
private readonly pendingBackendSaves = new Map<string, PluginStatePersistEntry>();
|
|
289
|
+
private backendSaveTimer: ReturnType<typeof setTimeout> | null = null;
|
|
290
|
+
private backendSaveInFlight: Promise<void> = Promise.resolve();
|
|
285
291
|
|
|
286
292
|
constructor(initial: Record<string, Record<string, unknown>>) {
|
|
287
293
|
for (const [pluginId, values] of Object.entries(initial)) {
|
|
@@ -304,7 +310,11 @@ class RemotePluginStateStore {
|
|
|
304
310
|
delete(pluginId: string, key: string): void {
|
|
305
311
|
this.state.get(pluginId)?.delete(key);
|
|
306
312
|
this.getScheduler(pluginId, key).cancel();
|
|
307
|
-
|
|
313
|
+
this.pendingBackendSaves.delete(this.schedulerKey(pluginId, key));
|
|
314
|
+
void this.backendSaveInFlight
|
|
315
|
+
.catch(() => {})
|
|
316
|
+
.then(() => backendRequest("pluginState.delete", { pluginId, key }))
|
|
317
|
+
.catch(() => {});
|
|
308
318
|
}
|
|
309
319
|
|
|
310
320
|
keys(pluginId: string): string[] {
|
|
@@ -317,20 +327,52 @@ class RemotePluginStateStore {
|
|
|
317
327
|
|
|
318
328
|
async flush(): Promise<void> {
|
|
319
329
|
await Promise.all([...this.schedulers.values()].map((scheduler) => scheduler.flush()));
|
|
330
|
+
await this.flushBackendSaves();
|
|
320
331
|
}
|
|
321
332
|
|
|
322
333
|
private getScheduler(pluginId: string, key: string) {
|
|
323
|
-
const schedulerKey =
|
|
334
|
+
const schedulerKey = this.schedulerKey(pluginId, key);
|
|
324
335
|
let scheduler = this.schedulers.get(schedulerKey);
|
|
325
336
|
if (!scheduler) {
|
|
326
337
|
scheduler = createPersistScheduler({
|
|
327
338
|
delayMs: PLUGIN_STATE_SAVE_DEBOUNCE_MS,
|
|
328
|
-
save: (entry) =>
|
|
339
|
+
save: (entry) => {
|
|
340
|
+
this.scheduleBackendSave(entry);
|
|
341
|
+
},
|
|
329
342
|
});
|
|
330
343
|
this.schedulers.set(schedulerKey, scheduler);
|
|
331
344
|
}
|
|
332
345
|
return scheduler;
|
|
333
346
|
}
|
|
347
|
+
|
|
348
|
+
private schedulerKey(pluginId: string, key: string): string {
|
|
349
|
+
return `${pluginId}\u0000${key}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private scheduleBackendSave(entry: PluginStatePersistEntry): void {
|
|
353
|
+
this.pendingBackendSaves.set(this.schedulerKey(entry.pluginId, entry.key), entry);
|
|
354
|
+
if (this.backendSaveTimer) return;
|
|
355
|
+
this.backendSaveTimer = setTimeout(() => {
|
|
356
|
+
void this.flushBackendSaves();
|
|
357
|
+
}, PLUGIN_STATE_BACKEND_FLUSH_DELAY_MS);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private async flushBackendSaves(): Promise<void> {
|
|
361
|
+
if (this.backendSaveTimer) {
|
|
362
|
+
clearTimeout(this.backendSaveTimer);
|
|
363
|
+
this.backendSaveTimer = null;
|
|
364
|
+
}
|
|
365
|
+
if (this.pendingBackendSaves.size === 0) return this.backendSaveInFlight;
|
|
366
|
+
|
|
367
|
+
const entries = [...this.pendingBackendSaves.values()];
|
|
368
|
+
this.pendingBackendSaves.clear();
|
|
369
|
+
const save = this.backendSaveInFlight
|
|
370
|
+
.catch(() => {})
|
|
371
|
+
.then(() => backendRequest<void>("pluginState.setMany", { entries }))
|
|
372
|
+
.catch(() => {});
|
|
373
|
+
this.backendSaveInFlight = save;
|
|
374
|
+
return save;
|
|
375
|
+
}
|
|
334
376
|
}
|
|
335
377
|
|
|
336
378
|
class RemotePersistence {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
import { Electroview } from "electrobun/view";
|
|
3
|
-
import { measurePerfAsync } from "../../../utils/perf-marks";
|
|
4
3
|
import {
|
|
5
4
|
type ApplicationMenuSelectMessage,
|
|
6
5
|
type CapabilityEventMessage,
|
|
@@ -119,13 +118,11 @@ async function waitForBridgeReady(): Promise<void> {
|
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
export async function backendRequest<T = unknown>(method: string, payload: unknown = null): Promise<T> {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
});
|
|
128
|
-
}, { method });
|
|
121
|
+
await waitForBridgeReady();
|
|
122
|
+
const result = await rpc.request["backend.request"]({
|
|
123
|
+
method,
|
|
124
|
+
payload: encodeRpcValue(payload),
|
|
125
|
+
});
|
|
129
126
|
return decodeRpcValue<T>(result);
|
|
130
127
|
}
|
|
131
128
|
|
|
@@ -40,8 +40,8 @@ function controlBorderColor(focused = false, active = false): string {
|
|
|
40
40
|
|
|
41
41
|
function controlShadow(active = false): string {
|
|
42
42
|
return active
|
|
43
|
-
?
|
|
44
|
-
:
|
|
43
|
+
? `0 0 0 1px ${blendHex(colors.bg, colors.borderFocused, 0.18)}, inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.06)}`
|
|
44
|
+
: `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.04)}`;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function buttonPalette(props: Pick<ButtonProps, "variant" | "active" | "disabled">) {
|
|
@@ -76,7 +76,7 @@ function buttonPalette(props: Pick<ButtonProps, "variant" | "active" | "disabled
|
|
|
76
76
|
case "ghost":
|
|
77
77
|
return {
|
|
78
78
|
fg: colors.textDim,
|
|
79
|
-
bg: "
|
|
79
|
+
bg: "transparent",
|
|
80
80
|
border: panelBorder(),
|
|
81
81
|
};
|
|
82
82
|
case "secondary":
|
|
@@ -339,7 +339,7 @@ function listRowStyle(selected: boolean): CSSProperties {
|
|
|
339
339
|
return {
|
|
340
340
|
borderRadius: CONTROL_RADIUS,
|
|
341
341
|
border: `1px solid ${selected ? colors.borderFocused : "transparent"}`,
|
|
342
|
-
boxShadow: selected ?
|
|
342
|
+
boxShadow: selected ? `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.06)}` : undefined,
|
|
343
343
|
cursor: "pointer",
|
|
344
344
|
};
|
|
345
345
|
}
|
|
@@ -5,7 +5,6 @@ import { App } from "../../../app";
|
|
|
5
5
|
import { UiHostProvider } from "../../../ui/host";
|
|
6
6
|
import { debugLog } from "../../../utils/debug-log";
|
|
7
7
|
import { measurePerfAsync } from "../../../utils/perf-marks";
|
|
8
|
-
import { startMainThreadMonitor } from "../../../utils/main-thread-monitor";
|
|
9
8
|
import { backendRequest, initElectrobunBackend } from "./backend-rpc";
|
|
10
9
|
import { installElectrobunAiHost } from "./ai-host";
|
|
11
10
|
import { installElectrobunBrokerRemoteClient } from "./broker-remote-client";
|
|
@@ -31,23 +30,9 @@ if (!rootElement) {
|
|
|
31
30
|
|
|
32
31
|
const root = createRoot(rootElement);
|
|
33
32
|
const bootLog = debugLog.createLogger("electrobun-web-boot");
|
|
34
|
-
const ELECTROBUN_WEB_CONSOLE_LOG_SOURCES = [
|
|
35
|
-
"app",
|
|
36
|
-
"main-thread",
|
|
37
|
-
"perf",
|
|
38
|
-
"refresh-queue",
|
|
39
|
-
"services",
|
|
40
|
-
"startup",
|
|
41
|
-
"electrobun-web-boot",
|
|
42
|
-
];
|
|
43
|
-
debugLog.mirrorToConsole({
|
|
44
|
-
sources: ELECTROBUN_WEB_CONSOLE_LOG_SOURCES,
|
|
45
|
-
});
|
|
46
|
-
const stopMainThreadMonitor = startMainThreadMonitor("electrobun.web", { mirrorToConsole: true });
|
|
47
33
|
|
|
48
34
|
rootElement.tabIndex = -1;
|
|
49
35
|
root.render(<div className="gloom-loading">Starting Gloomberb...</div>);
|
|
50
|
-
bootLog.warn("diagnostic console mirror enabled", { sources: ELECTROBUN_WEB_CONSOLE_LOG_SOURCES });
|
|
51
36
|
|
|
52
37
|
function focusWebSurface(): void {
|
|
53
38
|
window.focus();
|
|
@@ -111,7 +96,6 @@ async function boot() {
|
|
|
111
96
|
}
|
|
112
97
|
|
|
113
98
|
boot().catch((error) => {
|
|
114
|
-
stopMainThreadMonitor();
|
|
115
99
|
root.render(
|
|
116
100
|
<div className="gloom-fatal">
|
|
117
101
|
<h1>Gloomberb failed to start</h1>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
@keyframes gloom-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
2
|
-
html, body, #root { width: 100%; height: 100%; min-height: 100%; overflow: hidden; background:
|
|
2
|
+
html, body, #root { width: 100%; height: 100%; min-height: 100%; overflow: hidden; background: var(--gloom-bg); }
|
|
3
3
|
#root:focus { outline: none; }
|
|
4
4
|
body {
|
|
5
5
|
--cell-w: 8px;
|
|
@@ -8,7 +8,7 @@ body {
|
|
|
8
8
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
9
9
|
font-size: 12px;
|
|
10
10
|
line-height: var(--cell-h);
|
|
11
|
-
color:
|
|
11
|
+
color: var(--gloom-text);
|
|
12
12
|
cursor: default;
|
|
13
13
|
user-select: none;
|
|
14
14
|
color-scheme: dark;
|
|
@@ -62,26 +62,26 @@ button, [data-gloom-interactive="true"] { cursor: pointer; }
|
|
|
62
62
|
border: 0;
|
|
63
63
|
border-radius: 6px;
|
|
64
64
|
overflow: hidden;
|
|
65
|
-
box-shadow: 0 18px 38px
|
|
65
|
+
box-shadow: 0 18px 38px color-mix(in srgb, var(--gloom-bg) 46%, transparent);
|
|
66
66
|
}
|
|
67
67
|
[data-gloom-role="pane-window"][data-floating="true"]::after {
|
|
68
68
|
content: "";
|
|
69
69
|
position: absolute;
|
|
70
70
|
inset: 0;
|
|
71
|
-
border: 1px solid var(--pane-border-color,
|
|
71
|
+
border: 1px solid var(--pane-border-color, var(--gloom-border));
|
|
72
72
|
border-radius: inherit;
|
|
73
73
|
pointer-events: none;
|
|
74
74
|
z-index: 100;
|
|
75
75
|
}
|
|
76
76
|
[data-gloom-role="pane-window"][data-floating="true"][data-focused="true"] {
|
|
77
|
-
box-shadow: 0 18px 40px
|
|
77
|
+
box-shadow: 0 18px 40px color-mix(in srgb, var(--gloom-bg) 50%, transparent);
|
|
78
78
|
}
|
|
79
79
|
[data-gloom-role="pane-window"][data-floating="false"] {
|
|
80
80
|
border: 0;
|
|
81
81
|
border-radius: 0;
|
|
82
82
|
}
|
|
83
83
|
[data-gloom-role="pane-window"][data-floating="false"][data-focused="true"] {
|
|
84
|
-
box-shadow: inset 0 0 0 1px var(--pane-border-color,
|
|
84
|
+
box-shadow: inset 0 0 0 1px var(--pane-border-color, var(--gloom-border-focused));
|
|
85
85
|
}
|
|
86
86
|
[data-gloom-role="pane-header"] {
|
|
87
87
|
align-items: center;
|
|
@@ -125,7 +125,7 @@ body.gloom-dragging {
|
|
|
125
125
|
min-height: var(--cell-h);
|
|
126
126
|
max-height: var(--cell-h);
|
|
127
127
|
align-items: center;
|
|
128
|
-
border-top: 1px solid color-mix(in srgb,
|
|
128
|
+
border-top: 1px solid color-mix(in srgb, var(--gloom-border) 72%, transparent);
|
|
129
129
|
font-size: 11.5px;
|
|
130
130
|
}
|
|
131
131
|
[data-gloom-role="status-bar"] span {
|
|
@@ -137,7 +137,7 @@ body.gloom-dragging {
|
|
|
137
137
|
min-height: var(--cell-h);
|
|
138
138
|
max-height: var(--cell-h);
|
|
139
139
|
align-items: center;
|
|
140
|
-
border-top: 1px solid color-mix(in srgb, var(--pane-footer-border-color,
|
|
140
|
+
border-top: 1px solid color-mix(in srgb, var(--pane-footer-border-color, var(--gloom-border)) 72%, transparent);
|
|
141
141
|
font-size: 11.5px;
|
|
142
142
|
overflow: hidden;
|
|
143
143
|
}
|
|
@@ -153,7 +153,7 @@ body.gloom-dragging {
|
|
|
153
153
|
border-radius: 4px;
|
|
154
154
|
}
|
|
155
155
|
[data-gloom-role="pane-hint"]:hover {
|
|
156
|
-
background:
|
|
156
|
+
background: color-mix(in srgb, var(--gloom-text-bright) 8%, transparent);
|
|
157
157
|
}
|
|
158
158
|
[data-gloom-role="pane-action"],
|
|
159
159
|
[data-gloom-role="pane-close"] {
|
|
@@ -162,7 +162,7 @@ body.gloom-dragging {
|
|
|
162
162
|
}
|
|
163
163
|
[data-gloom-role="pane-action"]:hover,
|
|
164
164
|
[data-gloom-role="pane-close"]:hover {
|
|
165
|
-
background:
|
|
165
|
+
background: color-mix(in srgb, var(--gloom-text-bright) 8%, transparent);
|
|
166
166
|
}
|
|
167
167
|
[data-gloom-role="resize-handle"] {
|
|
168
168
|
cursor: nwse-resize;
|
|
@@ -191,7 +191,7 @@ body.gloom-dragging {
|
|
|
191
191
|
cursor: pointer;
|
|
192
192
|
}
|
|
193
193
|
[data-gloom-role="tab-close"]:hover {
|
|
194
|
-
background:
|
|
194
|
+
background: color-mix(in srgb, var(--gloom-text-bright) 10%, transparent);
|
|
195
195
|
}
|
|
196
196
|
[data-gloom-scrollbar-x],
|
|
197
197
|
[data-gloom-scrollbar-y] {
|
|
@@ -199,7 +199,7 @@ body.gloom-dragging {
|
|
|
199
199
|
scrollbar-width: thin;
|
|
200
200
|
}
|
|
201
201
|
[data-gloom-scrollbar-active="true"] {
|
|
202
|
-
scrollbar-color:
|
|
202
|
+
scrollbar-color: color-mix(in srgb, var(--gloom-text) 26%, transparent) transparent;
|
|
203
203
|
}
|
|
204
204
|
[data-gloom-scrollbar-x]::-webkit-scrollbar,
|
|
205
205
|
[data-gloom-scrollbar-y]::-webkit-scrollbar {
|
|
@@ -227,7 +227,7 @@ body.gloom-dragging {
|
|
|
227
227
|
}
|
|
228
228
|
[data-gloom-scrollbar-active="true"]::-webkit-scrollbar-thumb,
|
|
229
229
|
[data-gloom-scrollbar-active="true"]::-webkit-scrollbar-thumb:hover {
|
|
230
|
-
background:
|
|
230
|
+
background: color-mix(in srgb, var(--gloom-text) 24%, transparent);
|
|
231
231
|
background-clip: content-box;
|
|
232
232
|
}
|
|
233
233
|
[data-gloom-scrollbar-x]::-webkit-scrollbar-corner,
|
|
@@ -284,8 +284,8 @@ body.gloom-dragging {
|
|
|
284
284
|
top: 50%;
|
|
285
285
|
height: 1px;
|
|
286
286
|
}
|
|
287
|
-
.gloom-loading, .gloom-fatal { padding: 24px; color:
|
|
288
|
-
.gloom-fatal pre { white-space: pre-wrap; color:
|
|
287
|
+
.gloom-loading, .gloom-fatal { padding: 24px; color: var(--gloom-text); }
|
|
288
|
+
.gloom-fatal pre { white-space: pre-wrap; color: var(--gloom-negative); }
|
|
289
289
|
.gloom-toast-viewport {
|
|
290
290
|
position: fixed;
|
|
291
291
|
right: 16px;
|
|
@@ -300,7 +300,7 @@ body.gloom-dragging {
|
|
|
300
300
|
max-width: 420px;
|
|
301
301
|
padding: 10px 12px;
|
|
302
302
|
border: 1px solid;
|
|
303
|
-
box-shadow: 0 12px 24px
|
|
303
|
+
box-shadow: 0 12px 24px color-mix(in srgb, var(--gloom-bg) 28%, transparent);
|
|
304
304
|
}
|
|
305
305
|
.gloom-toast-action {
|
|
306
306
|
margin-top: 8px;
|
|
@@ -313,17 +313,19 @@ body.gloom-dragging {
|
|
|
313
313
|
display: flex;
|
|
314
314
|
align-items: center;
|
|
315
315
|
justify-content: center;
|
|
316
|
-
background:
|
|
316
|
+
background: color-mix(in srgb, var(--gloom-bg) 64%, transparent);
|
|
317
317
|
z-index: 9999;
|
|
318
318
|
}
|
|
319
319
|
.gloom-dialog {
|
|
320
320
|
max-width: min(920px, calc(100vw - 48px));
|
|
321
321
|
max-height: calc(100vh - 48px);
|
|
322
322
|
overflow: auto;
|
|
323
|
-
border: 1px solid
|
|
323
|
+
border: 1px solid var(--gloom-border);
|
|
324
324
|
border-radius: 6px;
|
|
325
|
-
background:
|
|
326
|
-
color:
|
|
325
|
+
background: var(--gloom-panel);
|
|
326
|
+
color: var(--gloom-text);
|
|
327
327
|
padding: 0;
|
|
328
|
-
box-shadow:
|
|
328
|
+
box-shadow:
|
|
329
|
+
0 18px 48px color-mix(in srgb, var(--gloom-bg) 46%, transparent),
|
|
330
|
+
inset 0 1px 0 color-mix(in srgb, var(--gloom-text-bright) 5%, transparent);
|
|
329
331
|
}
|
|
@@ -129,7 +129,7 @@ function commonStyle(props: Record<string, unknown>): CSSProperties {
|
|
|
129
129
|
marginBottom: cellInset(props.marginBottom ?? props.marginY ?? props.margin, "y"),
|
|
130
130
|
gap: typeof props.gap === "number" ? `${props.gap * gapUnit}px` : props.gap as CSSProperties["gap"],
|
|
131
131
|
overflow: props.overflow as CSSProperties["overflow"],
|
|
132
|
-
border: props.border ? `1px solid ${typeof props.borderColor === "string" ? props.borderColor : "
|
|
132
|
+
border: props.border ? `1px solid ${typeof props.borderColor === "string" ? props.borderColor : "var(--gloom-border)"}` : undefined,
|
|
133
133
|
boxSizing: "border-box",
|
|
134
134
|
minInlineSize: zeroMinInlineSize ? 0 : undefined,
|
|
135
135
|
minBlockSize: zeroMinBlockSize ? 0 : undefined,
|
|
@@ -551,7 +551,7 @@ function ChartCrosshair({
|
|
|
551
551
|
height: 7,
|
|
552
552
|
borderRadius: 7,
|
|
553
553
|
border: `1px solid ${crosshair.color}`,
|
|
554
|
-
backgroundColor:
|
|
554
|
+
backgroundColor: `color-mix(in srgb, ${crosshair.color} 16%, transparent)`,
|
|
555
555
|
boxSizing: "border-box",
|
|
556
556
|
transform: "translate(-50%, -50%)",
|
|
557
557
|
pointerEvents: "none",
|
|
@@ -610,7 +610,7 @@ function textInputStyle(props: Record<string, unknown>, multiline: boolean): CSS
|
|
|
610
610
|
resize: "none",
|
|
611
611
|
border: "none",
|
|
612
612
|
outline: "none",
|
|
613
|
-
color: typeof textColor === "string" ? textColor : "
|
|
613
|
+
color: typeof textColor === "string" ? textColor : "var(--gloom-text)",
|
|
614
614
|
backgroundColor: typeof backgroundColor === "string" ? backgroundColor : "transparent",
|
|
615
615
|
whiteSpace: multiline && props.wrapText ? "pre-wrap" : "pre",
|
|
616
616
|
overflow: multiline ? "auto" : "hidden",
|
|
@@ -886,13 +886,29 @@ const WebScrollBox = forwardRef<ScrollBoxRenderable, Record<string, unknown> & {
|
|
|
886
886
|
);
|
|
887
887
|
const horizontalScrollBarVisibleRef = useRef(horizontalScrollBarVisible);
|
|
888
888
|
const verticalScrollBarVisibleRef = useRef(verticalScrollBarVisible);
|
|
889
|
+
const horizontalScrollChangeHandlersRef = useRef(new Set<() => void>());
|
|
890
|
+
const verticalScrollChangeHandlersRef = useRef(new Set<() => void>());
|
|
889
891
|
const scrollFrameRef = useRef<number | null>(null);
|
|
890
892
|
const lastWheelAtRef = useRef(0);
|
|
891
893
|
const [scrollbarActive, markScrollbarActive] = useScrollbarActivity();
|
|
892
894
|
|
|
893
|
-
const getElement = () => elementRef.current;
|
|
895
|
+
const getElement = useCallback(() => elementRef.current, []);
|
|
894
896
|
const toCellY = (pixels: number) => Math.max(0, Math.round(pixels / WEB_CELL_HEIGHT));
|
|
895
897
|
const toCellX = (pixels: number) => Math.max(0, Math.round(pixels / WEB_CELL_WIDTH));
|
|
898
|
+
const emitHorizontalScrollChange = useCallback(() => {
|
|
899
|
+
for (const handler of horizontalScrollChangeHandlersRef.current) {
|
|
900
|
+
handler();
|
|
901
|
+
}
|
|
902
|
+
}, []);
|
|
903
|
+
const emitVerticalScrollChange = useCallback(() => {
|
|
904
|
+
for (const handler of verticalScrollChangeHandlersRef.current) {
|
|
905
|
+
handler();
|
|
906
|
+
}
|
|
907
|
+
}, []);
|
|
908
|
+
const emitScrollPositionChanges = useCallback(() => {
|
|
909
|
+
emitVerticalScrollChange();
|
|
910
|
+
emitHorizontalScrollChange();
|
|
911
|
+
}, [emitHorizontalScrollChange, emitVerticalScrollChange]);
|
|
896
912
|
const horizontalScrollBar = useMemo(() => ({
|
|
897
913
|
get visible() {
|
|
898
914
|
return horizontalScrollBarVisibleRef.current;
|
|
@@ -902,6 +918,12 @@ const WebScrollBox = forwardRef<ScrollBoxRenderable, Record<string, unknown> & {
|
|
|
902
918
|
horizontalScrollBarVisibleRef.current = normalized;
|
|
903
919
|
setHorizontalScrollBarVisible(normalized);
|
|
904
920
|
},
|
|
921
|
+
on(event: "change", handler: () => void) {
|
|
922
|
+
if (event === "change") horizontalScrollChangeHandlersRef.current.add(handler);
|
|
923
|
+
},
|
|
924
|
+
off(event: "change", handler: () => void) {
|
|
925
|
+
if (event === "change") horizontalScrollChangeHandlersRef.current.delete(handler);
|
|
926
|
+
},
|
|
905
927
|
}), []);
|
|
906
928
|
const verticalScrollBar = useMemo(() => ({
|
|
907
929
|
get visible() {
|
|
@@ -912,6 +934,12 @@ const WebScrollBox = forwardRef<ScrollBoxRenderable, Record<string, unknown> & {
|
|
|
912
934
|
verticalScrollBarVisibleRef.current = normalized;
|
|
913
935
|
setVerticalScrollBarVisible(normalized);
|
|
914
936
|
},
|
|
937
|
+
on(event: "change", handler: () => void) {
|
|
938
|
+
if (event === "change") verticalScrollChangeHandlersRef.current.add(handler);
|
|
939
|
+
},
|
|
940
|
+
off(event: "change", handler: () => void) {
|
|
941
|
+
if (event === "change") verticalScrollChangeHandlersRef.current.delete(handler);
|
|
942
|
+
},
|
|
915
943
|
}), []);
|
|
916
944
|
|
|
917
945
|
useEffect(() => {
|
|
@@ -938,28 +966,40 @@ const WebScrollBox = forwardRef<ScrollBoxRenderable, Record<string, unknown> & {
|
|
|
938
966
|
},
|
|
939
967
|
set scrollTop(value: number) {
|
|
940
968
|
const element = getElement();
|
|
941
|
-
if (element)
|
|
969
|
+
if (element) {
|
|
970
|
+
element.scrollTop = Math.max(0, value) * WEB_CELL_HEIGHT;
|
|
971
|
+
emitVerticalScrollChange();
|
|
972
|
+
}
|
|
942
973
|
},
|
|
943
974
|
get scrollTopPx() {
|
|
944
975
|
return Math.max(0, getElement()?.scrollTop ?? 0);
|
|
945
976
|
},
|
|
946
977
|
set scrollTopPx(value: number) {
|
|
947
978
|
const element = getElement();
|
|
948
|
-
if (element)
|
|
979
|
+
if (element) {
|
|
980
|
+
element.scrollTop = Math.max(0, value);
|
|
981
|
+
emitVerticalScrollChange();
|
|
982
|
+
}
|
|
949
983
|
},
|
|
950
984
|
get scrollLeft() {
|
|
951
985
|
return toCellX(getElement()?.scrollLeft ?? 0);
|
|
952
986
|
},
|
|
953
987
|
set scrollLeft(value: number) {
|
|
954
988
|
const element = getElement();
|
|
955
|
-
if (element)
|
|
989
|
+
if (element) {
|
|
990
|
+
element.scrollLeft = Math.max(0, value) * WEB_CELL_WIDTH;
|
|
991
|
+
emitHorizontalScrollChange();
|
|
992
|
+
}
|
|
956
993
|
},
|
|
957
994
|
get scrollLeftPx() {
|
|
958
995
|
return Math.max(0, getElement()?.scrollLeft ?? 0);
|
|
959
996
|
},
|
|
960
997
|
set scrollLeftPx(value: number) {
|
|
961
998
|
const element = getElement();
|
|
962
|
-
if (element)
|
|
999
|
+
if (element) {
|
|
1000
|
+
element.scrollLeft = Math.max(0, value);
|
|
1001
|
+
emitHorizontalScrollChange();
|
|
1002
|
+
}
|
|
963
1003
|
},
|
|
964
1004
|
get scrollHeight() {
|
|
965
1005
|
return toCellY(getElement()?.scrollHeight ?? 0);
|
|
@@ -1003,8 +1043,10 @@ const WebScrollBox = forwardRef<ScrollBoxRenderable, Record<string, unknown> & {
|
|
|
1003
1043
|
if (!element) return;
|
|
1004
1044
|
if (typeof target === "number") {
|
|
1005
1045
|
element.scrollTop = Math.max(0, target) * WEB_CELL_HEIGHT;
|
|
1046
|
+
emitVerticalScrollChange();
|
|
1006
1047
|
if (typeof y === "number") {
|
|
1007
1048
|
element.scrollLeft = Math.max(0, y) * WEB_CELL_WIDTH;
|
|
1049
|
+
emitHorizontalScrollChange();
|
|
1008
1050
|
}
|
|
1009
1051
|
return;
|
|
1010
1052
|
}
|
|
@@ -1012,14 +1054,17 @@ const WebScrollBox = forwardRef<ScrollBoxRenderable, Record<string, unknown> & {
|
|
|
1012
1054
|
left: Math.max(0, target.x ?? toCellX(element.scrollLeft)) * WEB_CELL_WIDTH,
|
|
1013
1055
|
top: Math.max(0, target.y ?? toCellY(element.scrollTop)) * WEB_CELL_HEIGHT,
|
|
1014
1056
|
});
|
|
1057
|
+
emitScrollPositionChanges();
|
|
1015
1058
|
},
|
|
1016
1059
|
scrollToPixels(target: number | { x?: number; y?: number }, y?: number) {
|
|
1017
1060
|
const element = getElement();
|
|
1018
1061
|
if (!element) return;
|
|
1019
1062
|
if (typeof target === "number") {
|
|
1020
1063
|
element.scrollTop = Math.max(0, target);
|
|
1064
|
+
emitVerticalScrollChange();
|
|
1021
1065
|
if (typeof y === "number") {
|
|
1022
1066
|
element.scrollLeft = Math.max(0, y);
|
|
1067
|
+
emitHorizontalScrollChange();
|
|
1023
1068
|
}
|
|
1024
1069
|
return;
|
|
1025
1070
|
}
|
|
@@ -1027,13 +1072,22 @@ const WebScrollBox = forwardRef<ScrollBoxRenderable, Record<string, unknown> & {
|
|
|
1027
1072
|
left: Math.max(0, target.x ?? element.scrollLeft),
|
|
1028
1073
|
top: Math.max(0, target.y ?? element.scrollTop),
|
|
1029
1074
|
});
|
|
1075
|
+
emitScrollPositionChanges();
|
|
1030
1076
|
},
|
|
1031
|
-
}), [
|
|
1077
|
+
}), [
|
|
1078
|
+
emitHorizontalScrollChange,
|
|
1079
|
+
emitScrollPositionChanges,
|
|
1080
|
+
emitVerticalScrollChange,
|
|
1081
|
+
getElement,
|
|
1082
|
+
horizontalScrollBar,
|
|
1083
|
+
verticalScrollBar,
|
|
1084
|
+
]);
|
|
1032
1085
|
|
|
1033
1086
|
const scrollable = props.scrollX === true || props.scrollY === true;
|
|
1034
1087
|
const overflowX = props.scrollX === true ? "auto" : "hidden";
|
|
1035
1088
|
const overflowY = props.scrollY === true ? "auto" : "hidden";
|
|
1036
1089
|
const handleScroll = useCallback(() => {
|
|
1090
|
+
emitScrollPositionChanges();
|
|
1037
1091
|
markScrollbarActive();
|
|
1038
1092
|
if (typeof props.onMouseScroll !== "function") return;
|
|
1039
1093
|
if (Date.now() - lastWheelAtRef.current < 32) return;
|
|
@@ -1042,7 +1096,7 @@ const WebScrollBox = forwardRef<ScrollBoxRenderable, Record<string, unknown> & {
|
|
|
1042
1096
|
scrollFrameRef.current = null;
|
|
1043
1097
|
(props.onMouseScroll as () => void)();
|
|
1044
1098
|
});
|
|
1045
|
-
}, [markScrollbarActive, props.onMouseScroll]);
|
|
1099
|
+
}, [emitScrollPositionChanges, markScrollbarActive, props.onMouseScroll]);
|
|
1046
1100
|
const handleWheel = useCallback((event: ReactWheelEvent<HTMLDivElement>) => {
|
|
1047
1101
|
markScrollbarActive();
|
|
1048
1102
|
lastWheelAtRef.current = Date.now();
|
|
@@ -1155,7 +1209,9 @@ function WebTabs({
|
|
|
1155
1209
|
|
|
1156
1210
|
const resolveTabBackground = (active: boolean, hovered: boolean) => {
|
|
1157
1211
|
if (active && variant === "pill") {
|
|
1158
|
-
return hovered
|
|
1212
|
+
return hovered
|
|
1213
|
+
? `color-mix(in srgb, ${palette.activeBg} 76%, ${palette.hoverBg})`
|
|
1214
|
+
: palette.activeBg;
|
|
1159
1215
|
}
|
|
1160
1216
|
return hovered ? palette.hoverBg : "transparent";
|
|
1161
1217
|
};
|