gloomberb 0.9.4 → 0.9.5

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.
Files changed (56) hide show
  1. package/package.json +3 -2
  2. package/patches/@opentui%2Fcore@0.3.2.patch +231 -29
  3. package/patches/react-reconciler@0.33.0.patch +10 -0
  4. package/src/cli/entry.ts +8 -1
  5. package/src/cli/native-smoke.ts +5 -0
  6. package/src/components/chart/native/bitmap-support.test.ts +27 -0
  7. package/src/components/chart/native/bitmap-support.ts +55 -0
  8. package/src/components/chart/native/kitty/index.test.ts +47 -0
  9. package/src/components/chart/native/surface/visibility.ts +8 -1
  10. package/src/components/chart/static/chart/bitmap.ts +16 -9
  11. package/src/components/command-bar/list/view.tsx +1 -1
  12. package/src/components/command-bar/theme-picker.tsx +1 -1
  13. package/src/components/data-table/view.tsx +0 -12
  14. package/src/components/layout/floating-pane.tsx +3 -3
  15. package/src/components/layout/pane/header.tsx +5 -0
  16. package/src/components/layout/pane/index.tsx +3 -3
  17. package/src/components/layout/shell/action-menu-overlay.tsx +1 -1
  18. package/src/components/layout/shell/cursor-occlusion.ts +9 -1
  19. package/src/components/layout/shell/index.tsx +8 -1
  20. package/src/components/layout/shell/pane/layers.tsx +2 -2
  21. package/src/components/layout/status-bar.tsx +5 -5
  22. package/src/components/pane-settings-dialog/desktop.tsx +1 -1
  23. package/src/components/price-performance.tsx +1 -1
  24. package/src/components/price-sparkline/view.tsx +15 -14
  25. package/src/components/speedometer-gauge/terminal.tsx +18 -15
  26. package/src/components/table-view-shared.tsx +0 -10
  27. package/src/components/ticker/list-table-view.tsx +1 -7
  28. package/src/components/ticker/list-table.test.tsx +0 -4
  29. package/src/components/ui/data-table/opentui/index.tsx +4 -10
  30. package/src/components/ui/data-table/types.ts +6 -4
  31. package/src/components/ui/list-view.tsx +1 -1
  32. package/src/components/ui/tabs.tsx +1 -2
  33. package/src/components/ui/ui.test.tsx +0 -9
  34. package/src/plugins/builtin/account-management/form-components.tsx +2 -2
  35. package/src/plugins/builtin/alerts/pane.tsx +1 -1
  36. package/src/plugins/builtin/buildout/cells.tsx +1 -1
  37. package/src/plugins/builtin/buildout/pane/index.tsx +1 -1
  38. package/src/plugins/builtin/chat/message/desktop.tsx +1 -1
  39. package/src/plugins/builtin/chat/message/inline-tokens.tsx +1 -1
  40. package/src/plugins/builtin/chat/message/terminal.tsx +2 -2
  41. package/src/plugins/builtin/chat/sidebar.tsx +1 -1
  42. package/src/plugins/builtin/chat/status-widget.tsx +1 -1
  43. package/src/plugins/builtin/cloud/auth-actions.tsx +2 -2
  44. package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +1 -1
  45. package/src/plugins/builtin/debug/index.tsx +3 -4
  46. package/src/plugins/builtin/help/components.tsx +1 -1
  47. package/src/plugins/builtin/holders/treemap.tsx +1 -1
  48. package/src/plugins/builtin/options/table.ts +5 -5
  49. package/src/plugins/ibkr/trade/ticket-panel.tsx +1 -1
  50. package/src/renderers/electrobun/view/data-table/index.tsx +0 -8
  51. package/src/renderers/electrobun/view/data-table/row.tsx +2 -12
  52. package/src/renderers/electrobun/view/host/box.tsx +6 -1
  53. package/src/renderers/electrobun/view/host/style.ts +1 -0
  54. package/src/renderers/electrobun/view/styles.css +7 -0
  55. package/src/renderers/opentui/native-smoke.ts +1 -0
  56. package/src/version.ts +1 -1
@@ -73,8 +73,6 @@ export interface DataTableViewProps<
73
73
  | "scrollRef"
74
74
  | "syncHeaderScroll"
75
75
  | "onBodyScrollActivity"
76
- | "hoveredIdx"
77
- | "setHoveredIdx"
78
76
  | "isSelected"
79
77
  | "onSelect"
80
78
  | "onActivate"
@@ -97,8 +95,6 @@ export interface DataTableViewProps<
97
95
  scrollRef?: RefObject<ScrollBoxRenderable | null>;
98
96
  syncHeaderScroll?: () => void;
99
97
  onBodyScrollActivity?: () => void;
100
- hoveredIdx?: number | null;
101
- setHoveredIdx?: (index: number | null) => void;
102
98
  keyboardNavigation?: boolean;
103
99
  onRootKeyDown?: (
104
100
  event: DataTableKeyEvent,
@@ -125,8 +121,6 @@ export function DataTableView<
125
121
  scrollRef,
126
122
  syncHeaderScroll,
127
123
  onBodyScrollActivity,
128
- hoveredIdx,
129
- setHoveredIdx,
130
124
  keyboardNavigation = true,
131
125
  onRootKeyDown,
132
126
  resetScrollKey,
@@ -137,15 +131,11 @@ export function DataTableView<
137
131
  const {
138
132
  effectiveHeaderScrollRef,
139
133
  effectiveScrollRef,
140
- effectiveHoveredIdx,
141
- effectiveSetHoveredIdx,
142
134
  effectiveSyncHeaderScroll,
143
135
  } = useTableViewState({
144
136
  headerScrollRef,
145
137
  scrollRef,
146
138
  syncHeaderScroll,
147
- hoveredIdx,
148
- setHoveredIdx,
149
139
  });
150
140
  const [cursorIndex, setCursorIndex] = useState<number | null>(null);
151
141
  const pendingCommitRef = useRef(false);
@@ -532,8 +522,6 @@ export function DataTableView<
532
522
  scrollRef={effectiveScrollRef}
533
523
  syncHeaderScroll={effectiveSyncHeaderScroll}
534
524
  onBodyScrollActivity={handleBodyScrollActivity}
535
- hoveredIdx={effectiveHoveredIdx}
536
- setHoveredIdx={effectiveSetHoveredIdx}
537
525
  scrollToIndex={effectiveScrollToIndex}
538
526
  scrollToIndexVersion={scrollToIndexVersion + selectionScrollVersion}
539
527
  isSelected={isItemSelected}
@@ -18,7 +18,7 @@ interface FloatingPaneWrapperProps {
18
18
  windowModeSelected?: boolean;
19
19
  showActions?: boolean;
20
20
  onMouseDown?: (event: any) => void;
21
- onMouseMove?: (event: any) => void;
21
+ onHeaderMouseMove?: (event: any) => void;
22
22
  onHeaderMouseDown?: (event: any) => void;
23
23
  onHeaderMouseDrag?: (event: any) => void;
24
24
  onHeaderMouseDragEnd?: (event: any) => void;
@@ -63,7 +63,7 @@ export function FloatingPaneWrapper({
63
63
  windowModeSelected = false,
64
64
  showActions = false,
65
65
  onMouseDown,
66
- onMouseMove,
66
+ onHeaderMouseMove,
67
67
  onHeaderMouseDown,
68
68
  onHeaderMouseDrag,
69
69
  onHeaderMouseDragEnd,
@@ -104,7 +104,6 @@ export function FloatingPaneWrapper({
104
104
  showBorderColor: true,
105
105
  })}
106
106
  onMouseDown={onMouseDown}
107
- onMouseMove={onMouseMove}
108
107
  >
109
108
  <PaneHeader
110
109
  title={title}
@@ -113,6 +112,7 @@ export function FloatingPaneWrapper({
113
112
  windowModeSelected={windowModeSelected}
114
113
  floating
115
114
  showActions={showActions}
115
+ onHeaderMouseMove={onHeaderMouseMove}
116
116
  onHeaderMouseDown={onHeaderMouseDown}
117
117
  onHeaderMouseDrag={onHeaderMouseDrag}
118
118
  onHeaderMouseDragEnd={onHeaderMouseDragEnd}
@@ -14,6 +14,7 @@ interface PaneHeaderProps {
14
14
  windowModeSelected?: boolean;
15
15
  floating?: boolean;
16
16
  showActions?: boolean;
17
+ onHeaderMouseMove?: (event: any) => void;
17
18
  onHeaderMouseDown?: (event: any) => void;
18
19
  onHeaderMouseDrag?: (event: any) => void;
19
20
  onHeaderMouseDragEnd?: (event: any) => void;
@@ -111,6 +112,7 @@ export function PaneHeader({
111
112
  windowModeSelected = false,
112
113
  floating = false,
113
114
  showActions = false,
115
+ onHeaderMouseMove,
114
116
  onHeaderMouseDown,
115
117
  onHeaderMouseDrag,
116
118
  onHeaderMouseDragEnd,
@@ -143,6 +145,7 @@ export function PaneHeader({
143
145
  data-focused={focused ? "true" : "false"}
144
146
  data-window-mode-selected={windowModeSelected ? "true" : "false"}
145
147
  onMouseDown={onHeaderMouseDown}
148
+ onMouseMove={onHeaderMouseMove}
146
149
  onMouseDrag={onHeaderMouseDrag}
147
150
  onMouseDragEnd={onHeaderMouseDragEnd}
148
151
  onContextMenu={onHeaderContextMenu}
@@ -226,6 +229,7 @@ export function PaneHeader({
226
229
  backgroundColor={backgroundColor}
227
230
  flexDirection="row"
228
231
  onMouseDown={handleTerminalHeaderMouseDown}
232
+ onMouseMove={onHeaderMouseMove}
229
233
  onMouseDrag={onHeaderMouseDrag}
230
234
  onMouseDragEnd={onHeaderMouseDragEnd}
231
235
  >
@@ -263,6 +267,7 @@ export function PaneHeader({
263
267
  backgroundColor={backgroundColor}
264
268
  flexDirection="row"
265
269
  onMouseDown={handleTerminalHeaderMouseDown}
270
+ onMouseMove={onHeaderMouseMove}
266
271
  onMouseDrag={onHeaderMouseDrag}
267
272
  onMouseDragEnd={onHeaderMouseDragEnd}
268
273
  >
@@ -16,7 +16,7 @@ interface PaneWrapperProps {
16
16
  flexGrow?: number;
17
17
  showActions?: boolean;
18
18
  onMouseDown?: (event: any) => void;
19
- onMouseMove?: (event: any) => void;
19
+ onHeaderMouseMove?: (event: any) => void;
20
20
  onHeaderMouseDown?: (event: any) => void;
21
21
  onHeaderMouseDrag?: (event: any) => void;
22
22
  onHeaderMouseDragEnd?: (event: any) => void;
@@ -36,7 +36,7 @@ export function PaneWrapper({
36
36
  flexGrow,
37
37
  showActions = false,
38
38
  onMouseDown,
39
- onMouseMove,
39
+ onHeaderMouseMove,
40
40
  onHeaderMouseDown,
41
41
  onHeaderMouseDrag,
42
42
  onHeaderMouseDragEnd,
@@ -76,7 +76,6 @@ export function PaneWrapper({
76
76
  showBorderColor: true,
77
77
  })}
78
78
  onMouseDown={onMouseDown}
79
- onMouseMove={onMouseMove}
80
79
  >
81
80
  {title && (
82
81
  <PaneHeader
@@ -85,6 +84,7 @@ export function PaneWrapper({
85
84
  focused={focused}
86
85
  windowModeSelected={windowModeSelected}
87
86
  showActions={showActions}
87
+ onHeaderMouseMove={onHeaderMouseMove}
88
88
  onHeaderMouseDown={onHeaderMouseDown}
89
89
  onHeaderMouseDrag={onHeaderMouseDrag}
90
90
  onHeaderMouseDragEnd={onHeaderMouseDragEnd}
@@ -52,7 +52,7 @@ export function ShellActionMenuOverlay({
52
52
  height={1}
53
53
  width={innerWidth}
54
54
  backgroundColor={hovered ? colors.selected : colors.panel}
55
- onMouseMove={() => onHoverItem(item.id)}
55
+ onMouseOver={() => onHoverItem(item.id)}
56
56
  onMouseDown={(mouseEvent: any) => {
57
57
  mouseEvent.stopPropagation();
58
58
  mouseEvent.preventDefault();
@@ -23,6 +23,8 @@ interface ResolveShellCursorOcclusionRectsOptions {
23
23
  width: number;
24
24
  }
25
25
 
26
+ const MAX_RENDERABLE_ANCESTOR_DEPTH = 256;
27
+
26
28
  export function resolveShellCursorOcclusionRects({
27
29
  contentHeight,
28
30
  dragFloatingRect,
@@ -102,7 +104,13 @@ function readRenderableBounds(renderable: unknown): LayoutBounds | null {
102
104
 
103
105
  function readRenderablePaneId(renderable: unknown): string | null {
104
106
  let current = renderable;
105
- while (current && typeof current === "object") {
107
+ const seen = new Set<object>();
108
+ let depth = 0;
109
+
110
+ while (current && typeof current === "object" && !seen.has(current)) {
111
+ if (depth >= MAX_RENDERABLE_ANCESTOR_DEPTH) return null;
112
+ seen.add(current);
113
+ depth += 1;
106
114
  const record = current as Record<string, unknown>;
107
115
  const paneId = record["data-gloom-pane-id"];
108
116
  if (typeof paneId === "string") return paneId;
@@ -525,7 +525,14 @@ export function Shell({
525
525
  height={nativePaneChrome ? undefined : contentHeight}
526
526
  position={nativePaneChrome ? "relative" : undefined}
527
527
  overflow="hidden"
528
- {...(!nativePaneChrome ? { onMouse: handleMouse } : {})}
528
+ {...(!nativePaneChrome
529
+ ? {
530
+ onMouseDown: handleMouse,
531
+ onMouseDrag: handleMouse,
532
+ onMouseDragEnd: handleMouse,
533
+ onMouseUp: handleMouse,
534
+ }
535
+ : {})}
529
536
  >
530
537
  <Box
531
538
  position="absolute"
@@ -132,7 +132,7 @@ export function ShellPaneLayers({
132
132
  windowModeSelected={windowModeSelected}
133
133
  footer={footer}
134
134
  onMouseDown={nativePaneChrome ? (event) => handleNativePaneMouseDown(leaf.instanceId, event) : undefined}
135
- onMouseMove={() => setHoveredPaneIfChanged(leaf.instanceId)}
135
+ onHeaderMouseMove={() => setHoveredPaneIfChanged(leaf.instanceId)}
136
136
  onHeaderMouseDown={nativePaneChrome && !transientFocusActive ? (event) => startNativeDockedDrag(leaf.instanceId, rect, event) : undefined}
137
137
  onHeaderMouseDrag={nativePaneChrome && !transientFocusActive ? handleNativeDrag : undefined}
138
138
  onHeaderMouseDragEnd={nativePaneChrome && !transientFocusActive ? handleNativeDrag : undefined}
@@ -192,7 +192,7 @@ export function ShellPaneLayers({
192
192
  showActions={showActions}
193
193
  footer={footer}
194
194
  onMouseDown={nativePaneChrome ? (event) => handleNativePaneMouseDown(pane.instance.instanceId, event) : undefined}
195
- onMouseMove={() => setHoveredPaneIfChanged(pane.instance.instanceId)}
195
+ onHeaderMouseMove={() => setHoveredPaneIfChanged(pane.instance.instanceId)}
196
196
  onHeaderMouseDown={nativePaneChrome ? (event) => startNativeFloatingDrag(pane.instance.instanceId, preview, event) : undefined}
197
197
  onHeaderMouseDrag={nativePaneChrome ? handleNativeDrag : undefined}
198
198
  onHeaderMouseDragEnd={nativePaneChrome ? handleNativeDrag : undefined}
@@ -364,7 +364,7 @@ function CommandBarHint({
364
364
  <Text
365
365
  fg={hovered ? colors.text : colors.textDim}
366
366
  {...(!nativePaneChrome ? { bg: hovered ? hoverBg() : undefined } : {})}
367
- onMouseMove={() => setHoveredControl((current) => (current === "command-bar" ? current : "command-bar"))}
367
+ onMouseOver={() => setHoveredControl((current) => (current === "command-bar" ? current : "command-bar"))}
368
368
  onMouseDown={openCommandBar}
369
369
  {...(nativePaneChrome ? { "data-gloom-interactive": "true" } : {})}
370
370
  >
@@ -385,7 +385,7 @@ function NativeGridlockTip({
385
385
  <Text
386
386
  fg={hoveredControl === "gridlock-tip" ? colors.textBright : colors.borderFocused}
387
387
  attributes={TextAttributes.BOLD}
388
- onMouseMove={() => setHoveredControl((current) => (current === "gridlock-tip" ? current : "gridlock-tip"))}
388
+ onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip" ? current : "gridlock-tip"))}
389
389
  onMouseDown={handleGridlockTip}
390
390
  data-gloom-interactive="true"
391
391
  >
@@ -393,7 +393,7 @@ function NativeGridlockTip({
393
393
  </Text>
394
394
  <Text
395
395
  fg={hoveredControl === "gridlock-tip-dismiss" ? colors.text : colors.textDim}
396
- onMouseMove={() => setHoveredControl((current) => (current === "gridlock-tip-dismiss" ? current : "gridlock-tip-dismiss"))}
396
+ onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip-dismiss" ? current : "gridlock-tip-dismiss"))}
397
397
  onMouseDown={dismissGridlockTip}
398
398
  data-gloom-interactive="true"
399
399
  >
@@ -415,14 +415,14 @@ function TerminalGridlockTip({
415
415
  <Box width={1} />
416
416
  <Box
417
417
  backgroundColor={hoveredControl === "gridlock-tip" ? hoverBg() : colors.header}
418
- onMouseMove={() => setHoveredControl((current) => (current === "gridlock-tip" ? current : "gridlock-tip"))}
418
+ onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip" ? current : "gridlock-tip"))}
419
419
  onMouseDown={handleGridlockTip}
420
420
  >
421
421
  <Text fg={colors.headerText}> Gridlock All </Text>
422
422
  </Box>
423
423
  <Text
424
424
  fg={hoveredControl === "gridlock-tip-dismiss" ? colors.text : colors.textDim}
425
- onMouseMove={() => setHoveredControl((current) => (current === "gridlock-tip-dismiss" ? current : "gridlock-tip-dismiss"))}
425
+ onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip-dismiss" ? current : "gridlock-tip-dismiss"))}
426
426
  onMouseDown={dismissGridlockTip}
427
427
  >
428
428
  {" x"}
@@ -183,7 +183,7 @@ function DesktopSettingsRow({
183
183
  flexDirection="column"
184
184
  minHeight={field.description ? undefined : 2}
185
185
  backgroundColor={hovered || selected ? desktopHoverSurface() : "transparent"}
186
- onMouseMove={onHover}
186
+ onMouseOver={onHover}
187
187
  onMouseDown={field.type === "select" ? undefined : (event: any) => {
188
188
  event.stopPropagation?.();
189
189
  event.preventDefault?.();
@@ -157,7 +157,7 @@ export function PriceReturnTable({
157
157
  height={1}
158
158
  width={width}
159
159
  backgroundColor={row.selected ? blendHex(colors.panel, colors.borderFocused, 0.18) : colors.panel}
160
- onMouseMove={() => onSelectSymbol(row.symbol)}
160
+ onMouseOver={() => onSelectSymbol(row.symbol)}
161
161
  onMouseDown={(event: any) => {
162
162
  onFocusInteraction?.(event);
163
163
  onSelectSymbol(row.symbol);
@@ -10,7 +10,7 @@ import {
10
10
  import { colors } from "../../theme/colors";
11
11
  import { useThemeColors } from "../../theme/theme-context";
12
12
  import type { PricePoint } from "../../types/financials";
13
- import { computeBitmapSize } from "../chart/native/chart-rasterizer";
13
+ import { resolveNativeBitmapSize, shouldRenderNativeBitmap } from "../chart/native/bitmap-support";
14
14
  import { renderSparklineBitmap } from "./bitmap";
15
15
  import {
16
16
  buildSamples,
@@ -90,22 +90,21 @@ function TerminalPriceSparkline({
90
90
  const { nativeCharts, cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1 } = useUiCapabilities();
91
91
  const nativeRenderer = useNativeRenderer();
92
92
  const rendererResolution = nativeRenderer.resolution;
93
+ const rendererCapabilities = nativeRenderer.capabilities;
93
94
  const rendererTerminalWidth = nativeRenderer.terminalWidth;
94
95
  const rendererTerminalHeight = nativeRenderer.terminalHeight;
95
96
  const bitmap = useMemo(() => {
96
- if (!nativeCharts || values.length < 2) return null;
97
- const scale = Math.max(1, pixelRatio);
98
- const bitmapSize = rendererResolution && rendererTerminalWidth > 0 && rendererTerminalHeight > 0
99
- ? computeBitmapSize(
100
- { x: 0, y: 0, width, height },
101
- rendererResolution,
102
- rendererTerminalWidth,
103
- rendererTerminalHeight,
104
- )
105
- : {
106
- pixelWidth: Math.max(1, Math.round(width * cellWidthPx * scale)),
107
- pixelHeight: Math.max(1, Math.round(height * cellHeightPx * scale)),
108
- };
97
+ if (values.length < 2 || !shouldRenderNativeBitmap(nativeRenderer, nativeCharts === true)) return null;
98
+ const bitmapSize = resolveNativeBitmapSize({
99
+ width,
100
+ height,
101
+ resolution: rendererResolution,
102
+ terminalWidth: rendererTerminalWidth,
103
+ terminalHeight: rendererTerminalHeight,
104
+ cellWidthPx,
105
+ cellHeightPx,
106
+ pixelRatio,
107
+ });
109
108
  return renderSparklineBitmap(values, bitmapSize.pixelWidth, bitmapSize.pixelHeight, color, { area, compact: height <= 1 });
110
109
  }, [
111
110
  area,
@@ -114,7 +113,9 @@ function TerminalPriceSparkline({
114
113
  color,
115
114
  height,
116
115
  nativeCharts,
116
+ nativeRenderer,
117
117
  pixelRatio,
118
+ rendererCapabilities,
118
119
  rendererResolution,
119
120
  rendererTerminalHeight,
120
121
  rendererTerminalWidth,
@@ -1,6 +1,7 @@
1
1
  import { useMemo } from "react";
2
2
  import { Box, ChartSurface, Span, Text, TextAttributes, useNativeRenderer, useUiCapabilities } from "../../ui";
3
- import { computeBitmapSize, type NativeChartBitmap } from "../chart/native/chart-rasterizer";
3
+ import type { NativeChartBitmap } from "../chart/native/chart-rasterizer";
4
+ import { resolveNativeBitmapSize, shouldRenderNativeBitmap } from "../chart/native/bitmap-support";
4
5
  import { drawCircle, drawLine, parseHex } from "../chart/native/raster/primitives";
5
6
  import { colors } from "../../theme/colors";
6
7
  import {
@@ -203,29 +204,29 @@ export function TerminalSpeedometerGauge({
203
204
  const labelRow = useMemo(() => renderLabelRow(gaugeWidth, min, max, segments), [gaugeWidth, max, min, segments]);
204
205
  const tickRow = useMemo(() => renderTickRow(gaugeWidth, min, max), [gaugeWidth, max, min]);
205
206
  const arcRows = useMemo(() => renderArcRows(value, min, max, gaugeWidth, segments), [gaugeWidth, max, min, segments, value]);
207
+ const rendererCapabilities = nativeRenderer.capabilities;
206
208
  const rendererResolution = nativeRenderer.resolution;
207
209
  const rendererTerminalWidth = nativeRenderer.terminalWidth;
208
210
  const rendererTerminalHeight = nativeRenderer.terminalHeight;
209
211
  const bitmap = useMemo<NativeChartBitmap | null>(() => {
210
- if (!nativeCharts) return null;
211
- const bitmapSize = rendererResolution && rendererTerminalWidth > 0 && rendererTerminalHeight > 0
212
- ? computeBitmapSize(
213
- { x: 0, y: 0, width: gaugeWidth, height: arcRows.length },
214
- rendererResolution,
215
- rendererTerminalWidth,
216
- rendererTerminalHeight,
217
- )
218
- : null;
219
- const scale = Math.max(1, pixelRatio);
220
- const pixelWidth = bitmapSize?.pixelWidth ?? Math.max(1, Math.round(gaugeWidth * cellWidthPx * scale));
221
- const pixelHeight = bitmapSize?.pixelHeight ?? Math.max(1, Math.round(arcRows.length * cellHeightPx * scale));
212
+ if (!shouldRenderNativeBitmap(nativeRenderer, nativeCharts === true)) return null;
213
+ const bitmapSize = resolveNativeBitmapSize({
214
+ width: gaugeWidth,
215
+ height: arcRows.length,
216
+ resolution: rendererResolution,
217
+ terminalWidth: rendererTerminalWidth,
218
+ terminalHeight: rendererTerminalHeight,
219
+ cellWidthPx,
220
+ cellHeightPx,
221
+ pixelRatio,
222
+ });
222
223
  return renderGaugeBitmap(
223
224
  value,
224
225
  min,
225
226
  max,
226
227
  segments,
227
- pixelWidth,
228
- pixelHeight,
228
+ bitmapSize.pixelWidth,
229
+ bitmapSize.pixelHeight,
229
230
  );
230
231
  }, [
231
232
  arcRows.length,
@@ -235,7 +236,9 @@ export function TerminalSpeedometerGauge({
235
236
  max,
236
237
  min,
237
238
  nativeCharts,
239
+ nativeRenderer,
238
240
  pixelRatio,
241
+ rendererCapabilities,
239
242
  rendererResolution,
240
243
  rendererTerminalHeight,
241
244
  rendererTerminalWidth,
@@ -2,7 +2,6 @@ import {
2
2
  useCallback,
3
3
  useEffect,
4
4
  useRef,
5
- useState,
6
5
  type ReactNode,
7
6
  type RefObject,
8
7
  } from "react";
@@ -114,23 +113,16 @@ export function useTableViewState({
114
113
  headerScrollRef,
115
114
  scrollRef,
116
115
  syncHeaderScroll,
117
- hoveredIdx,
118
- setHoveredIdx,
119
116
  }: {
120
117
  headerScrollRef?: RefObject<ScrollBoxRenderable | null>;
121
118
  scrollRef?: RefObject<ScrollBoxRenderable | null>;
122
119
  syncHeaderScroll?: () => void;
123
- hoveredIdx?: number | null;
124
- setHoveredIdx?: (index: number | null) => void;
125
120
  }) {
126
121
  const internalHeaderScrollRef = useRef<ScrollBoxRenderable>(null);
127
122
  const internalScrollRef = useRef<ScrollBoxRenderable>(null);
128
- const [internalHoveredIdx, setInternalHoveredIdx] = useState<number | null>(null);
129
123
 
130
124
  const effectiveHeaderScrollRef = headerScrollRef ?? internalHeaderScrollRef;
131
125
  const effectiveScrollRef = scrollRef ?? internalScrollRef;
132
- const effectiveHoveredIdx = hoveredIdx !== undefined ? hoveredIdx : internalHoveredIdx;
133
- const effectiveSetHoveredIdx = setHoveredIdx ?? setInternalHoveredIdx;
134
126
 
135
127
  const defaultSyncHeaderScroll = useCallback(() => {
136
128
  const body = effectiveScrollRef.current;
@@ -144,8 +136,6 @@ export function useTableViewState({
144
136
  return {
145
137
  effectiveHeaderScrollRef,
146
138
  effectiveScrollRef,
147
- effectiveHoveredIdx,
148
- effectiveSetHoveredIdx,
149
139
  effectiveSyncHeaderScroll: syncHeaderScroll ?? defaultSyncHeaderScroll,
150
140
  };
151
141
  }
@@ -56,8 +56,6 @@ export interface TickerListTableViewProps {
56
56
  scrollRef?: RefObject<ScrollBoxRenderable | null>;
57
57
  syncHeaderScroll?: () => void;
58
58
  onBodyScrollActivity?: () => void;
59
- hoveredIdx?: number | null;
60
- setHoveredIdx?: (index: number | null) => void;
61
59
  keyboardNavigation?: boolean;
62
60
  onRootKeyDown?: (event: DataTableKeyEvent) => boolean | void;
63
61
  onVisibleRangeChange?: (range: TickerListVisibleRange) => void;
@@ -119,8 +117,6 @@ export function TickerListTableView({
119
117
  scrollRef,
120
118
  syncHeaderScroll,
121
119
  onBodyScrollActivity,
122
- hoveredIdx,
123
- setHoveredIdx,
124
120
  keyboardNavigation = true,
125
121
  onRootKeyDown,
126
122
  onVisibleRangeChange,
@@ -187,7 +183,7 @@ export function TickerListTableView({
187
183
  ticker: TickerRecord,
188
184
  column: ColumnConfig,
189
185
  _index: number,
190
- rowState: { selected: boolean; hovered: boolean },
186
+ rowState: { selected: boolean },
191
187
  ) => {
192
188
  const financials = financialsMap.get(ticker.metadata.ticker);
193
189
  if (column.id === PRICE_SPARKLINE_COLUMN_ID) {
@@ -279,8 +275,6 @@ export function TickerListTableView({
279
275
  scrollRef={effectiveScrollRef}
280
276
  syncHeaderScroll={syncHeaderScroll}
281
277
  onBodyScrollActivity={handleBodyScrollActivity}
282
- hoveredIdx={hoveredIdx}
283
- setHoveredIdx={setHoveredIdx}
284
278
  keyboardNavigation={keyboardNavigation}
285
279
  onRootKeyDown={onRootKeyDown}
286
280
  resetScrollKey={resetScrollKey}
@@ -32,8 +32,6 @@ const manyTickers: TickerRecord[] = Array.from({ length: 1000 }, (_, index) => (
32
32
  },
33
33
  }));
34
34
 
35
- function noop(_index: number | null): void {}
36
-
37
35
  function resolveCell(_column: ColumnConfig, ticker: TickerRecord, _financials: TickerFinancials | undefined): TickerTableCell {
38
36
  resolveCellCallCount += 1;
39
37
  return { text: ticker.metadata.ticker };
@@ -69,8 +67,6 @@ function LargeTickerListTableHarness() {
69
67
  columns={columns}
70
68
  tickers={manyTickers}
71
69
  cursorSymbol={cursorSymbol}
72
- hoveredIdx={null}
73
- setHoveredIdx={noop}
74
70
  setCursorSymbol={setCursorSymbol}
75
71
  resolveCell={resolveCell}
76
72
  financialsMap={financialsMap}
@@ -40,8 +40,6 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
40
40
  scrollRef,
41
41
  syncHeaderScroll,
42
42
  onBodyScrollActivity,
43
- hoveredIdx,
44
- setHoveredIdx,
45
43
  headerScrollId,
46
44
  bodyScrollId,
47
45
  getItemKey,
@@ -369,8 +367,7 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
369
367
  }
370
368
 
371
369
  const selected = isSelected(item, index);
372
- const hovered = hoveredIdx === index && !selected;
373
- const rowState = { selected, hovered };
370
+ const rowState = { selected };
374
371
  const rowBackgroundColor = getRowBackgroundColor?.(
375
372
  item,
376
373
  index,
@@ -378,9 +375,8 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
378
375
  );
379
376
  const rowBg = selected
380
377
  ? colors.selected
381
- : hovered
382
- ? hoverBg()
383
- : rowBackgroundColor ?? colors.bg;
378
+ : rowBackgroundColor ?? colors.bg;
379
+ const rowHoverBg = selected ? undefined : hoverBg();
384
380
 
385
381
  return (
386
382
  <Box
@@ -390,10 +386,8 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
390
386
  {...tableContentWidthProps(contentWidth)}
391
387
  paddingX={horizontalPadding}
392
388
  backgroundColor={rowBg}
389
+ hoverBackgroundColor={rowHoverBg}
393
390
  data-gloom-context-menu-surface={rowContextMenuSurface ? "true" : undefined}
394
- onMouseMove={() => {
395
- if (hoveredIdx !== index) setHoveredIdx(index);
396
- }}
397
391
  onMouseDown={(event: any) => {
398
392
  focusPane();
399
393
  onTableMouseDown?.(event);
@@ -29,6 +29,10 @@ export interface DataTableSectionHeader {
29
29
 
30
30
  export type DataTableScrollAlign = "nearest" | "center";
31
31
 
32
+ export interface DataTableRowState {
33
+ selected: boolean;
34
+ }
35
+
32
36
  export interface DataTableProps<
33
37
  T,
34
38
  C extends DataTableColumn = DataTableColumn,
@@ -42,8 +46,6 @@ export interface DataTableProps<
42
46
  scrollRef: RefObject<ScrollBoxRenderable | null>;
43
47
  syncHeaderScroll: () => void;
44
48
  onBodyScrollActivity: () => void;
45
- hoveredIdx: number | null;
46
- setHoveredIdx: (index: number | null) => void;
47
49
  headerScrollId?: string;
48
50
  bodyScrollId?: string;
49
51
  getItemKey: (item: T, index: number) => string;
@@ -58,7 +60,7 @@ export interface DataTableProps<
58
60
  item: T,
59
61
  column: C,
60
62
  index: number,
61
- rowState: { selected: boolean; hovered: boolean },
63
+ rowState: DataTableRowState,
62
64
  ) => DataTableCell;
63
65
  renderSectionHeader?: (
64
66
  item: T,
@@ -67,7 +69,7 @@ export interface DataTableProps<
67
69
  getRowBackgroundColor?: (
68
70
  item: T,
69
71
  index: number,
70
- rowState: { selected: boolean; hovered: boolean },
72
+ rowState: DataTableRowState,
71
73
  ) => string | undefined;
72
74
  emptyContent?: ReactNode;
73
75
  bodyAfter?: ReactNode;
@@ -166,7 +166,7 @@ export function ListView({
166
166
  key={item.id}
167
167
  height={1}
168
168
  backgroundColor={rowBg}
169
- onMouseMove={() => {
169
+ onMouseOver={() => {
170
170
  if (!disabled) {
171
171
  setHoveredIndex((current) => (current === index ? current : index));
172
172
  if (selectOnHover) onSelect?.(index);
@@ -287,7 +287,6 @@ function OpenTuiTabs({
287
287
  flexDirection="row"
288
288
  backgroundColor={active && variant === "pill" ? palette.activeBg : hovered ? palette.hoverBg : undefined}
289
289
  onMouseOver={startHover}
290
- onMouseMove={startHover}
291
290
  onMouseOut={endHover}
292
291
  onMouseDown={selectTab}
293
292
  onDoubleClick={tab.disabled || !tab.onDoubleClick ? undefined : () => tab.onDoubleClick?.(tab.value)}
@@ -319,7 +318,7 @@ function OpenTuiTabs({
319
318
  width={addWidth}
320
319
  height={1}
321
320
  backgroundColor={hoveredValue === "__add__" ? palette.hoverBg : undefined}
322
- onMouseMove={() => setHoveredValue((current) => (current === "__add__" ? current : "__add__"))}
321
+ onMouseOver={() => setHoveredValue((current) => (current === "__add__" ? current : "__add__"))}
323
322
  onMouseOut={() => setHoveredValue((current) => (current === "__add__" ? null : current))}
324
323
  onMouseDown={(event: TabPointerEvent) => {
325
324
  event.preventDefault();
@@ -51,7 +51,6 @@ function DataTableActivationHarness() {
51
51
  const headerScrollRef = useRef<ScrollBoxRenderable>(null);
52
52
  const scrollRef = useRef<ScrollBoxRenderable>(null);
53
53
  const [selectedId, setSelectedId] = useState<string | null>(null);
54
- const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
55
54
 
56
55
  return (
57
56
  <DataTable
@@ -64,8 +63,6 @@ function DataTableActivationHarness() {
64
63
  scrollRef={scrollRef}
65
64
  syncHeaderScroll={() => {}}
66
65
  onBodyScrollActivity={() => {}}
67
- hoveredIdx={hoveredIdx}
68
- setHoveredIdx={setHoveredIdx}
69
66
  getItemKey={(row) => row.id}
70
67
  isSelected={(row) => selectedId === row.id}
71
68
  onSelect={(row) => {
@@ -89,7 +86,6 @@ function DataTableSectionHarness() {
89
86
  const headerScrollRef = useRef<ScrollBoxRenderable>(null);
90
87
  const scrollRef = useRef<ScrollBoxRenderable>(null);
91
88
  const [selectedId, setSelectedId] = useState<string | null>(null);
92
- const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
93
89
  const rows: SectionTableRow[] = [
94
90
  { kind: "header", id: "macro", label: "Macro Releases" },
95
91
  { kind: "row", id: "cpi", name: "CPI" },
@@ -106,8 +102,6 @@ function DataTableSectionHarness() {
106
102
  scrollRef={scrollRef}
107
103
  syncHeaderScroll={() => {}}
108
104
  onBodyScrollActivity={() => {}}
109
- hoveredIdx={hoveredIdx}
110
- setHoveredIdx={setHoveredIdx}
111
105
  getItemKey={(row) => row.id}
112
106
  isSelected={(row) => row.kind === "row" && row.id === selectedId}
113
107
  onSelect={(row) => {
@@ -211,7 +205,6 @@ function DataTableColumnGapHarness() {
211
205
  function DataTableVirtualizationHarness() {
212
206
  const headerScrollRef = useRef<ScrollBoxRenderable>(null);
213
207
  const scrollRef = useRef<ScrollBoxRenderable>(null);
214
- const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
215
208
  const rows = Array.from({ length: 100 }, (_, index) => ({
216
209
  id: `row-${index}`,
217
210
  name: `Row ${index}`,
@@ -237,8 +230,6 @@ function DataTableVirtualizationHarness() {
237
230
  scrollRef={scrollRef}
238
231
  syncHeaderScroll={() => {}}
239
232
  onBodyScrollActivity={() => {}}
240
- hoveredIdx={hoveredIdx}
241
- setHoveredIdx={setHoveredIdx}
242
233
  getItemKey={(row) => row.id}
243
234
  isSelected={() => false}
244
235
  onSelect={() => {}}