gloomberb 0.9.4 → 0.9.6
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 +49 -8
- package/package.json +4 -3
- package/patches/@opentui%2Fcore@0.3.2.patch +231 -29
- package/patches/react-reconciler@0.33.0.patch +10 -0
- package/src/app/global-shortcuts.test.tsx +20 -1
- package/src/app/global-shortcuts.ts +2 -0
- package/src/app/pane-runtime/index.tsx +11 -4
- package/src/app/pane-runtime/pane-settings-runtime.tsx +1 -1
- package/src/app.tsx +41 -17
- package/src/cli/commands/remote.ts +206 -0
- package/src/cli/entry.ts +8 -1
- package/src/cli/index.ts +2 -0
- package/src/cli/native-smoke.ts +5 -0
- package/src/components/chart/core/keyboard.ts +10 -1
- package/src/components/chart/native/bitmap-support.test.ts +27 -0
- package/src/components/chart/native/bitmap-support.ts +55 -0
- package/src/components/chart/native/kitty/index.test.ts +47 -0
- package/src/components/chart/native/surface/visibility.ts +8 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +4 -0
- package/src/components/chart/static/chart/bitmap.ts +16 -9
- package/src/components/chart/static/chart/surface.test.tsx +59 -4
- package/src/components/chart/static/chart/surface.tsx +4 -0
- package/src/components/chart/stock-chart/index.test.ts +7 -0
- package/src/components/command-bar/list/view.tsx +48 -1
- package/src/components/command-bar/multi-select-picker.tsx +7 -0
- package/src/components/command-bar/routing/navigation-state.ts +10 -1
- package/src/components/command-bar/surface/index.test.tsx +14 -0
- package/src/components/command-bar/theme-picker.tsx +91 -79
- package/src/components/command-bar/workflow/field-row.tsx +30 -5
- package/src/components/data-table/view.tsx +0 -12
- package/src/components/index.ts +7 -0
- package/src/components/layout/floating-pane.tsx +3 -3
- package/src/components/layout/pane/footer/index.tsx +23 -0
- package/src/components/layout/pane/header.tsx +5 -0
- package/src/components/layout/pane/index.tsx +3 -3
- package/src/components/layout/shell/action-menu-overlay.tsx +1 -1
- package/src/components/layout/shell/cursor-occlusion.ts +9 -1
- package/src/components/layout/shell/index.test.tsx +2 -0
- package/src/components/layout/shell/index.tsx +16 -4
- package/src/components/layout/shell/pane/actions.ts +21 -7
- package/src/components/layout/shell/pane/layers.tsx +2 -2
- package/src/components/layout/status-bar.tsx +5 -5
- package/src/components/metric-treemap/index.tsx +317 -0
- package/src/components/metric-treemap/layout.test.ts +77 -0
- package/src/components/metric-treemap/layout.ts +476 -0
- package/src/components/pane-settings-dialog/desktop.tsx +1 -1
- package/src/components/price-performance.tsx +1 -1
- package/src/components/price-sparkline/view.tsx +15 -14
- package/src/components/speedometer-gauge/terminal.tsx +18 -15
- package/src/components/table-view-shared.tsx +0 -10
- package/src/components/ticker/list-table-view.tsx +1 -7
- package/src/components/ticker/list-table.test.tsx +0 -4
- package/src/components/toggle-list.tsx +10 -0
- package/src/components/ui/button.tsx +12 -0
- package/src/components/ui/data-table/index.tsx +51 -0
- package/src/components/ui/data-table/opentui/index.tsx +4 -10
- package/src/components/ui/data-table/types.ts +6 -4
- package/src/components/ui/fields.tsx +18 -0
- package/src/components/ui/list-view.tsx +84 -4
- package/src/components/ui/message-composer.tsx +22 -0
- package/src/components/ui/tabs.tsx +46 -2
- package/src/components/ui/ui.test.tsx +0 -9
- package/src/core/state/app/layout-reducer.ts +7 -1
- package/src/core/state/app/layout.ts +6 -0
- package/src/core/state/app/state.ts +23 -1
- package/src/core/state/app/types.ts +4 -2
- package/src/plugins/builtin/account-management/form-components.tsx +2 -2
- package/src/plugins/builtin/alerts/pane.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +5 -18
- package/src/plugins/builtin/chat/chat.test.tsx +1 -7
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -1
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +1 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +2 -2
- package/src/plugins/builtin/chat/sidebar.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +2 -8
- package/src/plugins/builtin/cloud/auth-actions.tsx +2 -2
- package/src/plugins/builtin/cloud/plugin.tsx +4 -13
- package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +1 -1
- package/src/plugins/builtin/debug/index.tsx +3 -4
- package/src/plugins/builtin/help/components.tsx +1 -1
- package/src/plugins/builtin/holders/treemap.tsx +28 -297
- package/src/plugins/builtin/holders/types.ts +0 -34
- package/src/plugins/builtin/market-heatmap/data.test.ts +121 -0
- package/src/plugins/builtin/market-heatmap/data.ts +296 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +320 -0
- package/src/plugins/builtin/options/table.ts +5 -5
- package/src/plugins/builtin/plugin-groups.ts +2 -0
- package/src/plugins/builtin/portfolio-list/grid.tsx +197 -0
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +41 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +78 -22
- package/src/plugins/ibkr/gateway/service/index.test.ts +22 -0
- package/src/plugins/ibkr/gateway/service/index.ts +6 -9
- package/src/plugins/ibkr/gateway/service/native-loader.ts +12 -0
- package/src/plugins/ibkr/trade/ticket-panel.tsx +1 -1
- package/src/remote/app-host.tsx +77 -0
- package/src/remote/client.ts +34 -0
- package/src/remote/command-bar.ts +98 -0
- package/src/remote/controller-utils.ts +111 -0
- package/src/remote/controller.test.ts +365 -0
- package/src/remote/controller.ts +434 -0
- package/src/remote/endpoint.ts +41 -0
- package/src/remote/json-patch.test.ts +31 -0
- package/src/remote/json-patch.ts +89 -0
- package/src/remote/layout-helpers.ts +62 -0
- package/src/remote/resources.ts +186 -0
- package/src/remote/revision.ts +18 -0
- package/src/remote/schema.ts +143 -0
- package/src/remote/semantic-helpers.ts +161 -0
- package/src/remote/semantic-tree.tsx +112 -0
- package/src/remote/server.test.ts +50 -0
- package/src/remote/server.ts +121 -0
- package/src/remote/types.ts +119 -0
- package/src/renderers/electrobun/bun/index.ts +55 -1
- package/src/renderers/electrobun/shared/protocol.ts +11 -1
- package/src/renderers/electrobun/view/backend-rpc.ts +28 -1
- package/src/renderers/electrobun/view/build-assets.ts +1 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +0 -8
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -12
- package/src/renderers/electrobun/view/host/box.tsx +11 -1
- package/src/renderers/electrobun/view/host/mouse.test.ts +37 -0
- package/src/renderers/electrobun/view/host/mouse.ts +2 -0
- package/src/renderers/electrobun/view/host/style.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +5 -1
- package/src/renderers/electrobun/view/native-stubs/ibkr-native-loader.ts +3 -0
- package/src/renderers/electrobun/view/styles.css +7 -0
- package/src/renderers/opentui/chart-surface.test.tsx +101 -0
- package/src/renderers/opentui/chart-surface.tsx +15 -20
- package/src/renderers/opentui/native-smoke.ts +1 -0
- package/src/renderers/opentui/start.tsx +32 -0
- package/src/sources/yahoo-finance/http.ts +31 -4
- package/src/state/app/context/index.test.tsx +47 -0
- package/src/state/app/context/index.tsx +48 -18
- package/src/ui/host.tsx +1 -0
- package/src/ui/index.tsx +183 -7
- package/src/version.ts +1 -1
- package/src/plugins/builtin/holders/treemap-layout.ts +0 -231
|
@@ -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
|
|
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}
|
|
@@ -26,6 +26,9 @@ export interface ToggleListProps {
|
|
|
26
26
|
rowGap?: number;
|
|
27
27
|
rowHeight?: number;
|
|
28
28
|
surface?: "framed" | "plain";
|
|
29
|
+
remoteLabel?: string;
|
|
30
|
+
remoteScope?: string;
|
|
31
|
+
remoteMetadata?: Record<string, unknown>;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
function DesktopToggleRow({
|
|
@@ -124,6 +127,9 @@ export function ToggleList({
|
|
|
124
127
|
rowGap,
|
|
125
128
|
rowHeight,
|
|
126
129
|
surface,
|
|
130
|
+
remoteLabel,
|
|
131
|
+
remoteScope,
|
|
132
|
+
remoteMetadata,
|
|
127
133
|
}: ToggleListProps) {
|
|
128
134
|
const isDesktopWeb = useUiHost().kind === "desktop-web";
|
|
129
135
|
const listItems: ListViewItem[] = items.map((item) => ({
|
|
@@ -145,6 +151,10 @@ export function ToggleList({
|
|
|
145
151
|
rowGap={rowGap ?? (isDesktopWeb ? 0 : undefined)}
|
|
146
152
|
rowHeight={rowHeight}
|
|
147
153
|
surface={surface}
|
|
154
|
+
remoteLabel={remoteLabel}
|
|
155
|
+
remoteScope={remoteScope}
|
|
156
|
+
remoteMetadata={remoteMetadata}
|
|
157
|
+
remoteItemKind="toggle"
|
|
148
158
|
onSelect={onSelect}
|
|
149
159
|
onActivate={(item) => {
|
|
150
160
|
onToggle?.(item.id);
|
|
@@ -2,6 +2,7 @@ import { Box, Text, useUiHost } from "../../ui";
|
|
|
2
2
|
import { TextAttributes } from "../../ui";
|
|
3
3
|
import { type ComponentType } from "react";
|
|
4
4
|
import { colors } from "../../theme/colors";
|
|
5
|
+
import { useRemoteUiNode } from "../../remote/semantic-tree";
|
|
5
6
|
|
|
6
7
|
export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger";
|
|
7
8
|
|
|
@@ -45,6 +46,17 @@ export function Button({
|
|
|
45
46
|
shortcut,
|
|
46
47
|
width,
|
|
47
48
|
}: ButtonProps) {
|
|
49
|
+
useRemoteUiNode({
|
|
50
|
+
role: "button",
|
|
51
|
+
label,
|
|
52
|
+
disabled,
|
|
53
|
+
actions: {
|
|
54
|
+
press: () => {
|
|
55
|
+
if (!disabled) onPress?.();
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
metadata: { variant, active, shortcut },
|
|
59
|
+
});
|
|
48
60
|
const HostButton = useUiHost().Button as ComponentType<ButtonProps> | undefined;
|
|
49
61
|
if (HostButton) {
|
|
50
62
|
return (
|
|
@@ -5,6 +5,8 @@ import type {
|
|
|
5
5
|
DataTableColumn,
|
|
6
6
|
DataTableProps,
|
|
7
7
|
} from "./types";
|
|
8
|
+
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
9
|
+
import { resolveRemoteItemIndex } from "../../../remote/semantic-helpers";
|
|
8
10
|
|
|
9
11
|
export type {
|
|
10
12
|
DataTableCell,
|
|
@@ -16,6 +18,46 @@ export type {
|
|
|
16
18
|
export function DataTable<T, C extends DataTableColumn = DataTableColumn>(
|
|
17
19
|
props: DataTableProps<T, C>,
|
|
18
20
|
) {
|
|
21
|
+
useRemoteUiNode({
|
|
22
|
+
role: "table",
|
|
23
|
+
label: "Data table",
|
|
24
|
+
actions: {
|
|
25
|
+
selectRow: (input) => {
|
|
26
|
+
const index = resolveTableIndex(input, props);
|
|
27
|
+
const item = index >= 0 ? props.items[index] : undefined;
|
|
28
|
+
if (item) props.onSelect(item, index);
|
|
29
|
+
},
|
|
30
|
+
activateRow: (input) => {
|
|
31
|
+
const index = resolveTableIndex(input, props);
|
|
32
|
+
const item = index >= 0 ? props.items[index] : undefined;
|
|
33
|
+
if (item) {
|
|
34
|
+
props.onSelect(item, index);
|
|
35
|
+
props.onActivate?.(item, index);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
sort: (input) => {
|
|
39
|
+
const columnId = typeof input === "string"
|
|
40
|
+
? input
|
|
41
|
+
: input && typeof input === "object" && typeof (input as { columnId?: unknown }).columnId === "string"
|
|
42
|
+
? (input as { columnId: string }).columnId
|
|
43
|
+
: null;
|
|
44
|
+
if (columnId && props.columns.some((column) => column.id === columnId)) {
|
|
45
|
+
props.onHeaderClick(columnId);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
metadata: {
|
|
50
|
+
sortColumnId: props.sortColumnId,
|
|
51
|
+
sortDirection: props.sortDirection,
|
|
52
|
+
columns: props.columns.map((column) => ({ id: column.id, label: column.label })),
|
|
53
|
+
rows: props.items.slice(0, 200).map((item, index) => ({
|
|
54
|
+
index,
|
|
55
|
+
key: props.getItemKey(item, index),
|
|
56
|
+
selected: props.isSelected(item, index),
|
|
57
|
+
})),
|
|
58
|
+
rowCount: props.items.length,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
19
61
|
const HostDataTable = useUiHost().DataTable as
|
|
20
62
|
| ComponentType<DataTableProps<T, C>>
|
|
21
63
|
| undefined;
|
|
@@ -24,3 +66,12 @@ export function DataTable<T, C extends DataTableColumn = DataTableColumn>(
|
|
|
24
66
|
}
|
|
25
67
|
return <OpenTuiDataTable {...props} />;
|
|
26
68
|
}
|
|
69
|
+
|
|
70
|
+
function resolveTableIndex<T, C extends DataTableColumn>(
|
|
71
|
+
input: unknown,
|
|
72
|
+
props: DataTableProps<T, C>,
|
|
73
|
+
): number {
|
|
74
|
+
return resolveRemoteItemIndex(input, props.items, {
|
|
75
|
+
key: (item, index) => props.getItemKey(item, index),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -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
|
|
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
|
-
:
|
|
382
|
-
|
|
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:
|
|
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:
|
|
72
|
+
rowState: DataTableRowState,
|
|
71
73
|
) => string | undefined;
|
|
72
74
|
emptyContent?: ReactNode;
|
|
73
75
|
bodyAfter?: ReactNode;
|
|
@@ -2,6 +2,8 @@ import { Box, Input, Span, Text, useUiHost } from "../../ui";
|
|
|
2
2
|
import { useEffect, useRef, useState, type ComponentType, type RefObject } from "react";
|
|
3
3
|
import { type InputRenderable } from "../../ui";
|
|
4
4
|
import { colors } from "../../theme/colors";
|
|
5
|
+
import { useRemoteUiNode } from "../../remote/semantic-tree";
|
|
6
|
+
import { remoteStringValue } from "../../remote/semantic-helpers";
|
|
5
7
|
|
|
6
8
|
export interface TextFieldProps {
|
|
7
9
|
label?: string;
|
|
@@ -46,6 +48,22 @@ export function TextField({
|
|
|
46
48
|
placeholderColor = colors.textDim,
|
|
47
49
|
onMouseDown,
|
|
48
50
|
}: TextFieldProps) {
|
|
51
|
+
useRemoteUiNode({
|
|
52
|
+
role: "text-field",
|
|
53
|
+
label: label ?? placeholder,
|
|
54
|
+
actions: {
|
|
55
|
+
setValue: (input) => {
|
|
56
|
+
const nextValue = remoteStringValue(input);
|
|
57
|
+
onChange?.(nextValue);
|
|
58
|
+
},
|
|
59
|
+
submit: (input) => {
|
|
60
|
+
const nextValue = remoteStringValue(input, value ?? "");
|
|
61
|
+
onSubmit?.(nextValue);
|
|
62
|
+
},
|
|
63
|
+
blur: () => onBlur?.(value ?? ""),
|
|
64
|
+
},
|
|
65
|
+
metadata: { value, placeholder, focused, type, variant },
|
|
66
|
+
});
|
|
49
67
|
const HostTextField = useUiHost().TextField as ComponentType<TextFieldProps> | undefined;
|
|
50
68
|
if (HostTextField) {
|
|
51
69
|
return (
|
|
@@ -2,12 +2,19 @@ import { Box, ScrollBox, Text, useUiHost } from "../../ui";
|
|
|
2
2
|
import { useEffect, useRef, useState, type ComponentType, type ReactNode } from "react";
|
|
3
3
|
import { TextAttributes, type ScrollBoxRenderable } from "../../ui";
|
|
4
4
|
import { colors, hoverBg } from "../../theme/colors";
|
|
5
|
+
import { useRemoteUiNode } from "../../remote/semantic-tree";
|
|
6
|
+
import { resolveRemoteItemIndex } from "../../remote/semantic-helpers";
|
|
5
7
|
|
|
6
8
|
export interface ListViewItem {
|
|
7
9
|
id: string;
|
|
8
10
|
label: string;
|
|
9
11
|
description?: string;
|
|
10
12
|
detail?: string;
|
|
13
|
+
category?: string;
|
|
14
|
+
kind?: string;
|
|
15
|
+
right?: string;
|
|
16
|
+
checked?: boolean;
|
|
17
|
+
current?: boolean;
|
|
11
18
|
disabled?: boolean;
|
|
12
19
|
}
|
|
13
20
|
|
|
@@ -38,6 +45,13 @@ export interface ListViewProps {
|
|
|
38
45
|
scrollable?: boolean;
|
|
39
46
|
selectOnHover?: boolean;
|
|
40
47
|
autoScrollToIndex?: boolean;
|
|
48
|
+
onMouseScroll?: (event: any) => void;
|
|
49
|
+
remoteRole?: string;
|
|
50
|
+
remoteLabel?: string;
|
|
51
|
+
remoteScope?: string;
|
|
52
|
+
remoteItemKind?: string;
|
|
53
|
+
remoteItemCategory?: string;
|
|
54
|
+
remoteMetadata?: Record<string, unknown>;
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
function DefaultRow({
|
|
@@ -88,7 +102,49 @@ export function ListView({
|
|
|
88
102
|
scrollable = false,
|
|
89
103
|
selectOnHover = false,
|
|
90
104
|
autoScrollToIndex = true,
|
|
105
|
+
onMouseScroll,
|
|
106
|
+
remoteRole = "list",
|
|
107
|
+
remoteLabel,
|
|
108
|
+
remoteScope,
|
|
109
|
+
remoteItemKind,
|
|
110
|
+
remoteItemCategory,
|
|
111
|
+
remoteMetadata,
|
|
91
112
|
}: ListViewProps) {
|
|
113
|
+
useRemoteUiNode({
|
|
114
|
+
role: remoteRole,
|
|
115
|
+
label: remoteLabel ?? emptyMessage,
|
|
116
|
+
actions: {
|
|
117
|
+
select: (input) => {
|
|
118
|
+
const index = resolveListIndex(input, items);
|
|
119
|
+
if (index >= 0 && !items[index]?.disabled) onSelect?.(index);
|
|
120
|
+
},
|
|
121
|
+
activate: (input) => {
|
|
122
|
+
const index = resolveListIndex(input, items);
|
|
123
|
+
const item = index >= 0 ? items[index] : undefined;
|
|
124
|
+
if (item && !item.disabled) {
|
|
125
|
+
onSelect?.(index);
|
|
126
|
+
onActivate?.(item, index);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
metadata: {
|
|
131
|
+
...remoteMetadata,
|
|
132
|
+
scope: remoteScope,
|
|
133
|
+
selectedIndex,
|
|
134
|
+
items: items.map((item, index) => ({
|
|
135
|
+
index,
|
|
136
|
+
id: item.id,
|
|
137
|
+
label: item.label,
|
|
138
|
+
detail: item.detail,
|
|
139
|
+
category: item.category ?? remoteItemCategory,
|
|
140
|
+
kind: item.kind ?? remoteItemKind,
|
|
141
|
+
right: item.right,
|
|
142
|
+
checked: item.checked,
|
|
143
|
+
current: item.current,
|
|
144
|
+
disabled: item.disabled === true,
|
|
145
|
+
})),
|
|
146
|
+
},
|
|
147
|
+
});
|
|
92
148
|
const HostListView = useUiHost().ListView as ComponentType<ListViewProps> | undefined;
|
|
93
149
|
if (HostListView) {
|
|
94
150
|
return (
|
|
@@ -113,6 +169,13 @@ export function ListView({
|
|
|
113
169
|
scrollable={scrollable}
|
|
114
170
|
selectOnHover={selectOnHover}
|
|
115
171
|
autoScrollToIndex={autoScrollToIndex}
|
|
172
|
+
onMouseScroll={onMouseScroll}
|
|
173
|
+
remoteRole={remoteRole}
|
|
174
|
+
remoteLabel={remoteLabel}
|
|
175
|
+
remoteScope={remoteScope}
|
|
176
|
+
remoteItemKind={remoteItemKind}
|
|
177
|
+
remoteItemCategory={remoteItemCategory}
|
|
178
|
+
remoteMetadata={remoteMetadata}
|
|
116
179
|
/>
|
|
117
180
|
);
|
|
118
181
|
}
|
|
@@ -130,7 +193,7 @@ export function ListView({
|
|
|
130
193
|
const sb = scrollRef.current;
|
|
131
194
|
if (!sb) return;
|
|
132
195
|
const safeIndex = Math.min(activeScrollIndex, items.length - 1);
|
|
133
|
-
const viewportH = Math.max(sb.viewport
|
|
196
|
+
const viewportH = Math.max(sb.viewport?.height ?? 1, 1);
|
|
134
197
|
if (safeIndex < sb.scrollTop) {
|
|
135
198
|
sb.scrollTo(safeIndex);
|
|
136
199
|
} else if (safeIndex >= sb.scrollTop + viewportH) {
|
|
@@ -142,7 +205,9 @@ export function ListView({
|
|
|
142
205
|
if (!scrollable) return;
|
|
143
206
|
const sb = scrollRef.current;
|
|
144
207
|
if (!sb) return;
|
|
145
|
-
sb.verticalScrollBar
|
|
208
|
+
if (sb.verticalScrollBar) {
|
|
209
|
+
sb.verticalScrollBar.visible = items.length > (sb.viewport?.height ?? 0);
|
|
210
|
+
}
|
|
146
211
|
}, [items.length, height, flexGrow, scrollable]);
|
|
147
212
|
|
|
148
213
|
if (items.length === 0) {
|
|
@@ -166,12 +231,13 @@ export function ListView({
|
|
|
166
231
|
key={item.id}
|
|
167
232
|
height={1}
|
|
168
233
|
backgroundColor={rowBg}
|
|
169
|
-
|
|
234
|
+
onMouseOver={() => {
|
|
170
235
|
if (!disabled) {
|
|
171
236
|
setHoveredIndex((current) => (current === index ? current : index));
|
|
172
237
|
if (selectOnHover) onSelect?.(index);
|
|
173
238
|
}
|
|
174
239
|
}}
|
|
240
|
+
{...(onMouseScroll ? { onMouseScroll } : {})}
|
|
175
241
|
onMouseDown={() => {
|
|
176
242
|
if (disabled) return;
|
|
177
243
|
onSelect?.(index);
|
|
@@ -188,7 +254,14 @@ export function ListView({
|
|
|
188
254
|
return (
|
|
189
255
|
<Box flexDirection="column" height={height} flexGrow={flexGrow}>
|
|
190
256
|
{scrollable ? (
|
|
191
|
-
<ScrollBox
|
|
257
|
+
<ScrollBox
|
|
258
|
+
ref={scrollRef}
|
|
259
|
+
height={height}
|
|
260
|
+
flexGrow={flexGrow}
|
|
261
|
+
scrollY
|
|
262
|
+
focusable={false}
|
|
263
|
+
{...(onMouseScroll ? { onMouseScroll } : {})}
|
|
264
|
+
>
|
|
192
265
|
{rows}
|
|
193
266
|
</ScrollBox>
|
|
194
267
|
) : rows}
|
|
@@ -204,3 +277,10 @@ export function ListView({
|
|
|
204
277
|
</Box>
|
|
205
278
|
);
|
|
206
279
|
}
|
|
280
|
+
|
|
281
|
+
function resolveListIndex(input: unknown, items: ListViewItem[]): number {
|
|
282
|
+
return resolveRemoteItemIndex(input, items, {
|
|
283
|
+
id: (item) => item.id,
|
|
284
|
+
label: (item) => item.label,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
@@ -2,6 +2,8 @@ import { Box, Text, Textarea, useUiHost } from "../../ui";
|
|
|
2
2
|
import { type ComponentType, type RefObject } from "react";
|
|
3
3
|
import { type TextareaRenderable } from "../../ui";
|
|
4
4
|
import { colors } from "../../theme/colors";
|
|
5
|
+
import { useRemoteUiNode } from "../../remote/semantic-tree";
|
|
6
|
+
import { remoteStringValue } from "../../remote/semantic-helpers";
|
|
5
7
|
|
|
6
8
|
export interface MessageComposerProps {
|
|
7
9
|
inputRef?: RefObject<TextareaRenderable | null>;
|
|
@@ -54,6 +56,26 @@ export function MessageComposer({
|
|
|
54
56
|
keyBindings,
|
|
55
57
|
wrapText = false,
|
|
56
58
|
}: MessageComposerProps) {
|
|
59
|
+
useRemoteUiNode({
|
|
60
|
+
role: "message-composer",
|
|
61
|
+
label: placeholder || "Message composer",
|
|
62
|
+
actions: {
|
|
63
|
+
setValue: (input) => {
|
|
64
|
+
const value = remoteStringValue(input);
|
|
65
|
+
onFocusRequest?.();
|
|
66
|
+
onInput?.(value);
|
|
67
|
+
},
|
|
68
|
+
submit: () => {
|
|
69
|
+
onFocusRequest?.();
|
|
70
|
+
onSubmit?.();
|
|
71
|
+
},
|
|
72
|
+
focus: () => {
|
|
73
|
+
onFocusRequest?.();
|
|
74
|
+
inputRef?.current?.focus?.();
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
metadata: { focused, placeholder, height },
|
|
78
|
+
});
|
|
57
79
|
const HostMessageComposer = useUiHost().MessageComposer as ComponentType<MessageComposerProps> | undefined;
|
|
58
80
|
if (HostMessageComposer) {
|
|
59
81
|
return (
|
|
@@ -3,6 +3,7 @@ import { useShortcut } from "../../react/input";
|
|
|
3
3
|
import { Box, ScrollBox, Text, useUiHost } from "../../ui";
|
|
4
4
|
import { TextAttributes, type ScrollBoxRenderable } from "../../ui";
|
|
5
5
|
import { colors, hoverBg } from "../../theme/colors";
|
|
6
|
+
import { useRemoteUiNode } from "../../remote/semantic-tree";
|
|
6
7
|
|
|
7
8
|
type TabPointerEvent = {
|
|
8
9
|
button?: number;
|
|
@@ -76,6 +77,36 @@ export function Tabs({
|
|
|
76
77
|
navigationValueRef.current = value;
|
|
77
78
|
onSelect(value);
|
|
78
79
|
}, [onSelect]);
|
|
80
|
+
useRemoteUiNode({
|
|
81
|
+
role: "tabs",
|
|
82
|
+
label: "Tabs",
|
|
83
|
+
actions: {
|
|
84
|
+
select: (input) => {
|
|
85
|
+
const value = resolveTabValue(input, tabs);
|
|
86
|
+
if (!value) return;
|
|
87
|
+
const tab = tabs.find((entry) => entry.value === value);
|
|
88
|
+
if (!tab || tab.disabled) return;
|
|
89
|
+
handleSelect(value);
|
|
90
|
+
},
|
|
91
|
+
add: onAdd ? () => onAdd() : undefined,
|
|
92
|
+
close: (input) => {
|
|
93
|
+
const value = resolveTabValue(input, tabs);
|
|
94
|
+
if (!value) return;
|
|
95
|
+
const tab = tabs.find((entry) => entry.value === value);
|
|
96
|
+
if (!tab || tab.disabled) return;
|
|
97
|
+
tab.onClose?.(value);
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
metadata: {
|
|
101
|
+
activeValue,
|
|
102
|
+
tabs: tabs.map((tab) => ({
|
|
103
|
+
label: tab.label,
|
|
104
|
+
value: tab.value,
|
|
105
|
+
disabled: tab.disabled === true,
|
|
106
|
+
closeable: !!tab.onClose,
|
|
107
|
+
})),
|
|
108
|
+
},
|
|
109
|
+
});
|
|
79
110
|
|
|
80
111
|
const selectAdjacentTab = useCallback((direction: -1 | 1) => {
|
|
81
112
|
const enabledTabs = tabs.filter((tab) => !tab.disabled);
|
|
@@ -144,6 +175,20 @@ export function Tabs({
|
|
|
144
175
|
);
|
|
145
176
|
}
|
|
146
177
|
|
|
178
|
+
function resolveTabValue(input: unknown, tabs: TabItem[]): string | null {
|
|
179
|
+
if (typeof input === "string") return input;
|
|
180
|
+
if (typeof input === "number") return tabs[input]?.value ?? null;
|
|
181
|
+
if (input && typeof input === "object") {
|
|
182
|
+
const value = input as { value?: unknown; label?: unknown; index?: unknown };
|
|
183
|
+
if (typeof value.value === "string") return value.value;
|
|
184
|
+
if (typeof value.label === "string") {
|
|
185
|
+
return tabs.find((tab) => tab.label === value.label)?.value ?? null;
|
|
186
|
+
}
|
|
187
|
+
if (typeof value.index === "number") return tabs[value.index]?.value ?? null;
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
147
192
|
function tabWidth(
|
|
148
193
|
tab: TabItem,
|
|
149
194
|
active: boolean,
|
|
@@ -287,7 +332,6 @@ function OpenTuiTabs({
|
|
|
287
332
|
flexDirection="row"
|
|
288
333
|
backgroundColor={active && variant === "pill" ? palette.activeBg : hovered ? palette.hoverBg : undefined}
|
|
289
334
|
onMouseOver={startHover}
|
|
290
|
-
onMouseMove={startHover}
|
|
291
335
|
onMouseOut={endHover}
|
|
292
336
|
onMouseDown={selectTab}
|
|
293
337
|
onDoubleClick={tab.disabled || !tab.onDoubleClick ? undefined : () => tab.onDoubleClick?.(tab.value)}
|
|
@@ -319,7 +363,7 @@ function OpenTuiTabs({
|
|
|
319
363
|
width={addWidth}
|
|
320
364
|
height={1}
|
|
321
365
|
backgroundColor={hoveredValue === "__add__" ? palette.hoverBg : undefined}
|
|
322
|
-
|
|
366
|
+
onMouseOver={() => setHoveredValue((current) => (current === "__add__" ? current : "__add__"))}
|
|
323
367
|
onMouseOut={() => setHoveredValue((current) => (current === "__add__" ? null : current))}
|
|
324
368
|
onMouseDown={(event: TabPointerEvent) => {
|
|
325
369
|
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={() => {}}
|
|
@@ -64,7 +64,13 @@ export function reduceLayoutAction(state: AppState, action: AppAction): AppState
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
case "UPDATE_LAYOUT":
|
|
67
|
-
return withFocusedPane(
|
|
67
|
+
return withFocusedPane(
|
|
68
|
+
state,
|
|
69
|
+
{ ...state.config, layout: action.layout },
|
|
70
|
+
Object.prototype.hasOwnProperty.call(action, "focusedPaneId")
|
|
71
|
+
? { focusedPaneId: action.focusedPaneId ?? null }
|
|
72
|
+
: {},
|
|
73
|
+
);
|
|
68
74
|
|
|
69
75
|
case "SWITCH_LAYOUT": {
|
|
70
76
|
if (action.index < 0 || action.index >= state.config.layouts.length) return state;
|