gloomberb 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -54
- package/package.json +14 -13
- package/patches/{@opentui%2Fcore@0.1.90.patch → @opentui%2Fcore@0.3.2.patch} +175 -90
- package/src/api-client/chat.ts +8 -0
- package/src/api-client/index.test.ts +30 -0
- package/src/api-client/index.ts +4 -0
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/types.ts +1 -0
- package/src/app/global-shortcuts.test.tsx +54 -4
- package/src/app/global-shortcuts.ts +11 -5
- package/src/capabilities/factories.ts +4 -0
- package/src/components/chart/comparison/data.test.ts +54 -0
- package/src/components/chart/comparison/data.ts +47 -24
- package/src/components/chart/core/projection.ts +51 -27
- package/src/components/command-bar/action-runtime.ts +1 -0
- package/src/components/command-bar/layout-items/current-layout.test.ts +115 -0
- package/src/components/command-bar/layout-items/current-layout.ts +33 -0
- package/src/components/command-bar/list/view.tsx +3 -1
- package/src/components/command-bar/pane-templates/items.ts +4 -1
- package/src/components/command-bar/panel/body.tsx +1 -0
- package/src/components/command-bar/routes/root/results.ts +0 -30
- package/src/components/command-bar/routes/root/runtime.ts +2 -20
- package/src/components/command-bar/routes/ticker-search/actions.ts +42 -4
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -1
- package/src/components/command-bar/routing/launch-request.ts +14 -7
- package/src/components/command-bar/routing/list-state.ts +1 -3
- package/src/components/command-bar/surface/index.test.tsx +76 -4
- package/src/components/command-bar/surface/index.tsx +0 -2
- package/src/components/command-bar/surface/pane.test.tsx +1 -0
- package/src/components/command-bar/surface/test-harness.tsx +1 -0
- package/src/components/command-bar/view-model.ts +3 -3
- package/src/components/data-table/stack-view.test.tsx +14 -11
- package/src/components/data-table/stack-view.tsx +1 -2
- package/src/components/data-table/view.test.tsx +24 -18
- package/src/components/data-table/view.tsx +313 -83
- package/src/components/feed-data-table/stack-view.tsx +6 -8
- package/src/components/layout/pane/footer/index.test.tsx +6 -0
- package/src/components/layout/pane/footer/index.tsx +14 -2
- package/src/components/layout/pane/title.ts +5 -1
- package/src/components/layout/shell/index.test.tsx +120 -2
- package/src/components/layout/shell/index.tsx +2 -0
- package/src/components/layout/shell/menu.ts +1 -7
- package/src/components/layout/shell/pane/actions.ts +8 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +12 -2
- package/src/components/layout/shell/shortcuts.ts +14 -6
- package/src/components/layout/shell/window-mode/index.ts +8 -3
- package/src/components/layout/window-edit/presentation.ts +4 -4
- package/src/components/onboarding/onboarding-steps.tsx +3 -2
- package/src/components/pane-settings-dialog/field-dialogs.tsx +0 -2
- package/src/components/pane-settings-dialog/tui.tsx +1 -1
- package/src/components/ticker/list-table-view.tsx +17 -25
- package/src/components/ui/choice-dialog.tsx +12 -2
- package/src/components/ui/multi-select/dialog.tsx +17 -5
- package/src/components/ui/remote-image.tsx +1 -14
- package/src/components/ui/shortcut-hint.tsx +14 -2
- package/src/components/ui/table-layout.test.ts +2 -2
- package/src/components/ui/table-layout.ts +8 -2
- package/src/components/ui/tabs.tsx +115 -86
- package/src/components/ui/ui.test.tsx +83 -4
- package/src/core/state/app/types.ts +12 -4
- package/src/market-data/coordinator/auxiliary.ts +29 -1
- package/src/market-data/coordinator/index.ts +17 -1
- package/src/market-data/hooks.tsx +17 -1
- package/src/market-data/selectors.ts +4 -0
- package/src/plugins/builtin/account-management/form-components.tsx +0 -4
- package/src/plugins/builtin/account-management/model.test.ts +54 -0
- package/src/plugins/builtin/account-management/model.ts +13 -3
- package/src/plugins/builtin/account-management/pane.tsx +5 -11
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +5 -3
- package/src/plugins/builtin/analytics/index.tsx +0 -3
- package/src/plugins/builtin/analytics/view.tsx +6 -5
- package/src/plugins/builtin/broker-manager/index.tsx +6 -6
- package/src/plugins/builtin/buildout/pane/index.tsx +6 -6
- package/src/plugins/builtin/changelog/index.tsx +40 -27
- package/src/plugins/builtin/changelog/model.test.ts +77 -0
- package/src/plugins/builtin/changelog/model.ts +98 -0
- package/src/plugins/builtin/chat/channel-labels.ts +53 -0
- package/src/plugins/builtin/chat/channels.test.ts +28 -0
- package/src/plugins/builtin/chat/channels.ts +36 -19
- package/src/plugins/builtin/chat/chat.test.tsx +264 -5
- package/src/plugins/builtin/chat/content/channel-navigation.ts +50 -11
- package/src/plugins/builtin/chat/content/composer-runtime.test.tsx +144 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +151 -20
- package/src/plugins/builtin/chat/content/composer.tsx +23 -2
- package/src/plugins/builtin/chat/content/index.tsx +66 -4
- package/src/plugins/builtin/chat/content/layout-metrics.ts +3 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +27 -1
- package/src/plugins/builtin/chat/content/transcript.tsx +8 -0
- package/src/plugins/builtin/chat/content/types.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +41 -0
- package/src/plugins/builtin/chat/controller/messages.ts +18 -0
- package/src/plugins/builtin/chat/controller/notifications.ts +10 -4
- package/src/plugins/builtin/chat/controller/utils.ts +6 -3
- package/src/plugins/builtin/chat/controller/view.ts +1 -1
- package/src/plugins/builtin/chat/controller.test.ts +189 -0
- package/src/plugins/builtin/chat/edit-window.ts +24 -0
- package/src/plugins/builtin/chat/layout.ts +94 -31
- package/src/plugins/builtin/chat/message/action-chip.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +53 -20
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +16 -10
- package/src/plugins/builtin/chat/message/render-state.ts +5 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +49 -20
- package/src/plugins/builtin/chat/message/types.ts +2 -0
- package/src/plugins/builtin/chat/pane.tsx +35 -7
- package/src/plugins/builtin/chat/sidebar.test.tsx +72 -0
- package/src/plugins/builtin/chat/status-widget.tsx +29 -11
- package/src/plugins/builtin/chat/test-harness.tsx +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +7 -11
- package/src/plugins/builtin/congress-trades/pane.tsx +14 -22
- package/src/plugins/builtin/earnings/index.tsx +8 -7
- package/src/plugins/builtin/econ/index.tsx +6 -8
- package/src/plugins/builtin/help/components.tsx +1 -1
- package/src/plugins/builtin/help/index.test.tsx +105 -30
- package/src/plugins/builtin/help/index.tsx +326 -164
- package/src/plugins/builtin/help/shortcut-model.ts +5 -6
- package/src/plugins/builtin/holders/pane.tsx +7 -6
- package/src/plugins/builtin/market-movers/index.tsx +6 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +7 -6
- package/src/plugins/builtin/options/view.tsx +16 -8
- package/src/plugins/builtin/portfolio-list/column-values.ts +304 -1
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +93 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +7 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +25 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +281 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +27 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +4 -2
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +10 -68
- package/src/plugins/builtin/research/analyst-pane.tsx +1 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +358 -7
- package/src/plugins/builtin/research/index.test.ts +30 -1
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +6 -8
- package/src/plugins/builtin/sec/filing-documents.ts +66 -0
- package/src/plugins/builtin/sec/index.tsx +132 -20
- package/src/plugins/builtin/sectors/index.tsx +13 -23
- package/src/plugins/builtin/shared/ticker-request.ts +1 -0
- package/src/plugins/builtin/substack/api/auth.ts +185 -0
- package/src/plugins/builtin/substack/api/cache.ts +78 -0
- package/src/plugins/builtin/substack/api/loaders.ts +146 -0
- package/src/plugins/builtin/substack/api/store.ts +186 -0
- package/src/plugins/builtin/substack/api/types.ts +40 -0
- package/src/plugins/builtin/substack/api.test.ts +298 -0
- package/src/plugins/builtin/substack/article-detail.tsx +359 -0
- package/src/plugins/builtin/substack/article-stack.tsx +129 -0
- package/src/plugins/builtin/substack/content.ts +478 -0
- package/src/plugins/builtin/substack/feed-tabs.tsx +41 -0
- package/src/plugins/builtin/substack/index.tsx +49 -0
- package/src/plugins/builtin/substack/login-view.tsx +126 -0
- package/src/plugins/builtin/substack/model.test.ts +155 -0
- package/src/plugins/builtin/substack/normalize.ts +343 -0
- package/src/plugins/builtin/substack/pane-footer.ts +71 -0
- package/src/plugins/builtin/substack/pane-state.ts +170 -0
- package/src/plugins/builtin/substack/pane.tsx +500 -0
- package/src/plugins/builtin/substack/table.ts +111 -0
- package/src/plugins/builtin/substack/types.ts +71 -0
- package/src/plugins/builtin/substack/utils.ts +131 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +26 -25
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +6 -5
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +10 -8
- package/src/plugins/builtin/ticker-detail/index.test.tsx +16 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/pane.tsx +13 -1
- package/src/plugins/builtin/world-indices/index.tsx +9 -6
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/pane-manager/layout-state.ts +11 -0
- package/src/plugins/pane-manager.ts +1 -1
- package/src/plugins/prediction-markets/pane.tsx +10 -9
- package/src/plugins/registry/index.ts +3 -0
- package/src/plugins/runtime/context.tsx +1 -0
- package/src/plugins/runtime/index.tsx +4 -0
- package/src/react/use-throttled-commit-value.ts +116 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +42 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +3 -2
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -2
- package/src/renderers/electrobun/view/host/input.tsx +23 -2
- package/src/renderers/electrobun/view/http-fetch.ts +10 -3
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +4 -0
- package/src/sources/provider-router/cache.ts +1 -0
- package/src/sources/provider-router/document-routes.ts +45 -0
- package/src/sources/provider-router/index.ts +4 -0
- package/src/sources/sec-edgar/content.ts +9 -3
- package/src/sources/sec-edgar.test.ts +108 -1
- package/src/sources/sec-edgar.ts +105 -1
- package/src/sources/yahoo-finance.ts +4 -0
- package/src/state/app/context/index.test.tsx +16 -0
- package/src/test-support/plugin-runtime.ts +1 -0
- package/src/tickers/search/index.ts +1 -0
- package/src/types/data-provider.ts +11 -0
- package/src/ui/host.tsx +1 -0
- package/src/version.ts +1 -1
|
@@ -24,6 +24,41 @@ import {
|
|
|
24
24
|
|
|
25
25
|
export type DataTableKeyEvent = TableViewKeyEvent;
|
|
26
26
|
|
|
27
|
+
const DATA_TABLE_SELECTION_COMMIT_DELAY_MS = 150;
|
|
28
|
+
|
|
29
|
+
export type DataTableSelectionChangeReason =
|
|
30
|
+
| "keyboard"
|
|
31
|
+
| "pointer"
|
|
32
|
+
| "activation";
|
|
33
|
+
|
|
34
|
+
export type DataTableSelection<T> =
|
|
35
|
+
| { kind: "none" }
|
|
36
|
+
| {
|
|
37
|
+
kind: "index";
|
|
38
|
+
selectedIndex: number | null;
|
|
39
|
+
onChange: (
|
|
40
|
+
index: number,
|
|
41
|
+
item: T,
|
|
42
|
+
reason: DataTableSelectionChangeReason,
|
|
43
|
+
) => void;
|
|
44
|
+
}
|
|
45
|
+
| {
|
|
46
|
+
kind: "id";
|
|
47
|
+
selectedId: string | null;
|
|
48
|
+
getId: (item: T, index: number) => string;
|
|
49
|
+
onChange: (
|
|
50
|
+
id: string,
|
|
51
|
+
item: T,
|
|
52
|
+
index: number,
|
|
53
|
+
reason: DataTableSelectionChangeReason,
|
|
54
|
+
) => void;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
interface SelectionCommitTarget {
|
|
58
|
+
index: number;
|
|
59
|
+
id?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
27
62
|
export interface DataTableViewProps<
|
|
28
63
|
T,
|
|
29
64
|
C extends DataTableColumn = DataTableColumn,
|
|
@@ -35,11 +70,18 @@ export interface DataTableViewProps<
|
|
|
35
70
|
| "onBodyScrollActivity"
|
|
36
71
|
| "hoveredIdx"
|
|
37
72
|
| "setHoveredIdx"
|
|
73
|
+
| "isSelected"
|
|
74
|
+
| "onSelect"
|
|
75
|
+
| "onActivate"
|
|
38
76
|
> {
|
|
39
77
|
focused?: boolean;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
78
|
+
selection: DataTableSelection<T>;
|
|
79
|
+
onActivate?: (item: T, index: number) => void;
|
|
80
|
+
onCursorChange?: (
|
|
81
|
+
item: T,
|
|
82
|
+
index: number,
|
|
83
|
+
reason: DataTableSelectionChangeReason,
|
|
84
|
+
) => void;
|
|
43
85
|
isNavigable?: (item: T, index: number) => boolean;
|
|
44
86
|
rootBefore?: ReactNode;
|
|
45
87
|
rootAfter?: ReactNode;
|
|
@@ -62,9 +104,9 @@ export function DataTableView<
|
|
|
62
104
|
C extends DataTableColumn = DataTableColumn,
|
|
63
105
|
>({
|
|
64
106
|
focused = false,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
107
|
+
selection,
|
|
108
|
+
onActivate,
|
|
109
|
+
onCursorChange,
|
|
68
110
|
isNavigable,
|
|
69
111
|
rootBefore,
|
|
70
112
|
rootAfter,
|
|
@@ -97,29 +139,32 @@ export function DataTableView<
|
|
|
97
139
|
hoveredIdx,
|
|
98
140
|
setHoveredIdx,
|
|
99
141
|
});
|
|
100
|
-
const [
|
|
101
|
-
|
|
102
|
-
>(null);
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
? tableProps.items.findIndex(tableProps.isSelected)
|
|
106
|
-
: -1,
|
|
107
|
-
[selectedIndex, tableProps.items, tableProps.isSelected],
|
|
108
|
-
);
|
|
109
|
-
const usesFallbackSelection =
|
|
110
|
-
selectedIndex == null && selectedIndexFromPredicate < 0;
|
|
111
|
-
const effectiveSelectedIndex = selectedIndex
|
|
112
|
-
?? (selectedIndexFromPredicate >= 0
|
|
113
|
-
? selectedIndexFromPredicate
|
|
114
|
-
: fallbackSelectedIndex ?? -1);
|
|
115
|
-
const effectiveSelectedIndexRef = useRef(effectiveSelectedIndex);
|
|
116
|
-
effectiveSelectedIndexRef.current = effectiveSelectedIndex;
|
|
117
|
-
const effectiveScrollToIndex = scrollToIndex ?? (
|
|
118
|
-
effectiveSelectedIndex >= 0 ? effectiveSelectedIndex : null
|
|
142
|
+
const [cursorIndex, setCursorIndex] = useState<number | null>(null);
|
|
143
|
+
const pendingCommitRef = useRef(false);
|
|
144
|
+
const pendingCommitTargetRef = useRef<SelectionCommitTarget | null>(null);
|
|
145
|
+
const pendingCommitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
|
|
146
|
+
null,
|
|
119
147
|
);
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
148
|
+
const selectionKey = selection.kind === "id"
|
|
149
|
+
? selection.selectedId
|
|
150
|
+
: selection.kind === "index"
|
|
151
|
+
? selection.selectedIndex
|
|
152
|
+
: null;
|
|
153
|
+
const selectedIndexFromSelection = useMemo(() => {
|
|
154
|
+
if (selection.kind === "none") return -1;
|
|
155
|
+
if (selection.kind === "index") {
|
|
156
|
+
const index = selection.selectedIndex;
|
|
157
|
+
return typeof index === "number"
|
|
158
|
+
&& index >= 0
|
|
159
|
+
&& index < tableProps.items.length
|
|
160
|
+
? index
|
|
161
|
+
: -1;
|
|
162
|
+
}
|
|
163
|
+
if (selection.selectedId == null) return -1;
|
|
164
|
+
return tableProps.items.findIndex(
|
|
165
|
+
(item, index) => selection.getId(item, index) === selection.selectedId,
|
|
166
|
+
);
|
|
167
|
+
}, [selection, selectionKey, tableProps.items]);
|
|
123
168
|
const navigableIndices = useMemo(() => {
|
|
124
169
|
if (!isNavigable) return null;
|
|
125
170
|
return tableProps.items.reduce<number[]>((indices, item, index) => {
|
|
@@ -128,39 +173,82 @@ export function DataTableView<
|
|
|
128
173
|
}, []);
|
|
129
174
|
}, [isNavigable, tableProps.items]);
|
|
130
175
|
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
return;
|
|
176
|
+
const isValidCursorIndex = useCallback((index: number | null) => {
|
|
177
|
+
if (index == null || index < 0 || index >= tableProps.items.length) {
|
|
178
|
+
return false;
|
|
135
179
|
}
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
180
|
+
if (!isNavigable) return true;
|
|
181
|
+
const item = tableProps.items[index];
|
|
182
|
+
return item !== undefined && isNavigable(item, index);
|
|
183
|
+
}, [isNavigable, tableProps.items]);
|
|
184
|
+
|
|
185
|
+
const defaultCursorIndex = selection.kind === "none"
|
|
186
|
+
? -1
|
|
187
|
+
: isValidCursorIndex(selectedIndexFromSelection)
|
|
188
|
+
? selectedIndexFromSelection
|
|
189
|
+
: navigableIndices
|
|
190
|
+
? navigableIndices[0] ?? -1
|
|
191
|
+
: tableProps.items.length > 0
|
|
192
|
+
? 0
|
|
193
|
+
: -1;
|
|
194
|
+
const effectiveSelectedIndex = selection.kind === "none"
|
|
195
|
+
? -1
|
|
196
|
+
: isValidCursorIndex(cursorIndex)
|
|
197
|
+
? cursorIndex!
|
|
198
|
+
: defaultCursorIndex;
|
|
199
|
+
const effectiveSelectedIndexRef = useRef(effectiveSelectedIndex);
|
|
200
|
+
effectiveSelectedIndexRef.current = effectiveSelectedIndex;
|
|
201
|
+
const [selectionScrollVersion, setSelectionScrollVersion] = useState(0);
|
|
202
|
+
const [selectionScrollTarget, setSelectionScrollTarget] = useState<number | null>(null);
|
|
203
|
+
const lastExternalSelectionScrollRef = useRef<{
|
|
204
|
+
kind: DataTableSelection<T>["kind"];
|
|
205
|
+
key: string | number | null;
|
|
206
|
+
resolved: boolean;
|
|
207
|
+
} | null>(null);
|
|
208
|
+
const effectiveScrollToIndex = scrollToIndex !== undefined
|
|
209
|
+
? scrollToIndex
|
|
210
|
+
: selectionScrollTarget;
|
|
211
|
+
|
|
212
|
+
const requestSelectionScroll = useCallback((index: number) => {
|
|
213
|
+
if (scrollToIndex !== undefined || index < 0) return;
|
|
214
|
+
setSelectionScrollTarget(index);
|
|
215
|
+
setSelectionScrollVersion((current) => current + 1);
|
|
216
|
+
}, [scrollToIndex]);
|
|
217
|
+
|
|
218
|
+
const clearPendingCommit = useCallback(() => {
|
|
219
|
+
if (pendingCommitTimerRef.current) {
|
|
220
|
+
clearTimeout(pendingCommitTimerRef.current);
|
|
221
|
+
pendingCommitTimerRef.current = null;
|
|
222
|
+
}
|
|
223
|
+
pendingCommitRef.current = false;
|
|
224
|
+
pendingCommitTargetRef.current = null;
|
|
225
|
+
}, []);
|
|
226
|
+
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (selection.kind === "none") {
|
|
229
|
+
clearPendingCommit();
|
|
230
|
+
setCursorIndex(null);
|
|
147
231
|
return;
|
|
148
232
|
}
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
const nextIndex = navigableIndices.find(
|
|
152
|
-
(index) => index >= fallbackSelectedIndex,
|
|
153
|
-
)
|
|
154
|
-
?? navigableIndices.at(-1)
|
|
155
|
-
?? null;
|
|
156
|
-
setFallbackSelectedIndex(nextIndex);
|
|
233
|
+
if (pendingCommitRef.current) return;
|
|
234
|
+
setCursorIndex(defaultCursorIndex >= 0 ? defaultCursorIndex : null);
|
|
157
235
|
}, [
|
|
158
|
-
|
|
236
|
+
clearPendingCommit,
|
|
237
|
+
defaultCursorIndex,
|
|
159
238
|
navigableIndices,
|
|
239
|
+
selectedIndexFromSelection,
|
|
240
|
+
selection.kind,
|
|
241
|
+
selectionKey,
|
|
160
242
|
tableProps.items.length,
|
|
161
|
-
usesFallbackSelection,
|
|
162
243
|
]);
|
|
163
244
|
|
|
245
|
+
useEffect(() => () => {
|
|
246
|
+
if (pendingCommitTimerRef.current) {
|
|
247
|
+
clearTimeout(pendingCommitTimerRef.current);
|
|
248
|
+
pendingCommitTimerRef.current = null;
|
|
249
|
+
}
|
|
250
|
+
}, []);
|
|
251
|
+
|
|
164
252
|
const handleBodyScrollActivity = useTableBodyScrollActivity({
|
|
165
253
|
onBodyScrollActivity,
|
|
166
254
|
syncHeaderScroll: effectiveSyncHeaderScroll,
|
|
@@ -173,40 +261,156 @@ export function DataTableView<
|
|
|
173
261
|
});
|
|
174
262
|
|
|
175
263
|
useEffect(() => {
|
|
176
|
-
|
|
177
|
-
|
|
264
|
+
const current = {
|
|
265
|
+
kind: selection.kind,
|
|
266
|
+
key: selectionKey,
|
|
267
|
+
resolved: selectedIndexFromSelection >= 0,
|
|
268
|
+
};
|
|
269
|
+
const previous = lastExternalSelectionScrollRef.current;
|
|
270
|
+
lastExternalSelectionScrollRef.current = current;
|
|
271
|
+
|
|
272
|
+
if (selection.kind === "none") {
|
|
273
|
+
if (scrollToIndex === undefined) setSelectionScrollTarget(null);
|
|
178
274
|
return;
|
|
179
275
|
}
|
|
180
|
-
if (
|
|
181
|
-
lastSelectionScrollIndexRef.current = effectiveScrollToIndex;
|
|
182
|
-
setSelectionScrollVersion((current) => current + 1);
|
|
183
|
-
}, [effectiveScrollToIndex]);
|
|
276
|
+
if (effectiveSelectedIndex < 0) return;
|
|
184
277
|
|
|
185
|
-
|
|
278
|
+
const selectionChanged = !previous
|
|
279
|
+
|| previous.kind !== current.kind
|
|
280
|
+
|| previous.key !== current.key;
|
|
281
|
+
const selectedRowAppeared = !previous?.resolved && current.resolved;
|
|
282
|
+
if (selectionChanged || selectedRowAppeared) {
|
|
283
|
+
requestSelectionScroll(effectiveSelectedIndex);
|
|
284
|
+
}
|
|
285
|
+
}, [
|
|
286
|
+
effectiveSelectedIndex,
|
|
287
|
+
requestSelectionScroll,
|
|
288
|
+
scrollToIndex,
|
|
289
|
+
selectedIndexFromSelection,
|
|
290
|
+
selection.kind,
|
|
291
|
+
selectionKey,
|
|
292
|
+
]);
|
|
293
|
+
|
|
294
|
+
const commitIndex = useCallback((
|
|
295
|
+
index: number,
|
|
296
|
+
reason: DataTableSelectionChangeReason,
|
|
297
|
+
) => {
|
|
298
|
+
if (selection.kind === "none") return;
|
|
186
299
|
if (index < 0 || index >= tableProps.items.length) return;
|
|
187
300
|
const item = tableProps.items[index]!;
|
|
188
301
|
if (isNavigable && !isNavigable(item, index)) return;
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
302
|
+
|
|
303
|
+
if (selection.kind === "index") {
|
|
304
|
+
selection.onChange(index, item, reason);
|
|
305
|
+
return;
|
|
192
306
|
}
|
|
193
|
-
|
|
194
|
-
|
|
307
|
+
|
|
308
|
+
selection.onChange(selection.getId(item, index), item, index, reason);
|
|
309
|
+
}, [isNavigable, selection, tableProps.items]);
|
|
310
|
+
const commitIndexRef = useRef(commitIndex);
|
|
311
|
+
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
commitIndexRef.current = commitIndex;
|
|
314
|
+
}, [commitIndex]);
|
|
315
|
+
|
|
316
|
+
const getCommitTarget = useCallback((index: number): SelectionCommitTarget | null => {
|
|
317
|
+
if (selection.kind === "none") return null;
|
|
318
|
+
if (index < 0 || index >= tableProps.items.length) return null;
|
|
319
|
+
const item = tableProps.items[index]!;
|
|
320
|
+
if (isNavigable && !isNavigable(item, index)) return null;
|
|
321
|
+
return selection.kind === "id"
|
|
322
|
+
? { index, id: selection.getId(item, index) }
|
|
323
|
+
: { index };
|
|
324
|
+
}, [isNavigable, selection, tableProps.items]);
|
|
325
|
+
|
|
326
|
+
const commitTarget = useCallback((
|
|
327
|
+
target: SelectionCommitTarget | null,
|
|
328
|
+
reason: DataTableSelectionChangeReason,
|
|
329
|
+
) => {
|
|
330
|
+
if (!target) return;
|
|
331
|
+
if (selection.kind !== "id" || target.id == null) {
|
|
332
|
+
commitIndexRef.current(target.index, reason);
|
|
195
333
|
return;
|
|
196
334
|
}
|
|
197
|
-
tableProps.
|
|
198
|
-
|
|
335
|
+
const currentIndex = tableProps.items.findIndex(
|
|
336
|
+
(item, index) => selection.getId(item, index) === target.id,
|
|
337
|
+
);
|
|
338
|
+
if (currentIndex < 0) return;
|
|
339
|
+
commitIndexRef.current(currentIndex, reason);
|
|
340
|
+
}, [selection, tableProps.items]);
|
|
199
341
|
|
|
200
|
-
const
|
|
342
|
+
const commitIndexImmediately = useCallback((
|
|
343
|
+
index: number,
|
|
344
|
+
reason: DataTableSelectionChangeReason,
|
|
345
|
+
) => {
|
|
346
|
+
const target = getCommitTarget(index);
|
|
347
|
+
clearPendingCommit();
|
|
348
|
+
commitTarget(target, reason);
|
|
349
|
+
}, [clearPendingCommit, commitTarget, getCommitTarget]);
|
|
350
|
+
|
|
351
|
+
const scheduleCommitIndex = useCallback((index: number) => {
|
|
352
|
+
if (selection.kind === "none") return;
|
|
353
|
+
const target = getCommitTarget(index);
|
|
354
|
+
if (!target) return;
|
|
355
|
+
if (pendingCommitTimerRef.current) {
|
|
356
|
+
clearTimeout(pendingCommitTimerRef.current);
|
|
357
|
+
}
|
|
358
|
+
pendingCommitRef.current = true;
|
|
359
|
+
pendingCommitTargetRef.current = target;
|
|
360
|
+
pendingCommitTimerRef.current = setTimeout(() => {
|
|
361
|
+
pendingCommitTimerRef.current = null;
|
|
362
|
+
pendingCommitRef.current = false;
|
|
363
|
+
const pendingTarget = pendingCommitTargetRef.current;
|
|
364
|
+
pendingCommitTargetRef.current = null;
|
|
365
|
+
commitTarget(pendingTarget, "keyboard");
|
|
366
|
+
}, DATA_TABLE_SELECTION_COMMIT_DELAY_MS);
|
|
367
|
+
}, [commitTarget, getCommitTarget, selection.kind]);
|
|
368
|
+
|
|
369
|
+
const updateCursorIndex = useCallback((
|
|
370
|
+
index: number,
|
|
371
|
+
options: {
|
|
372
|
+
commit: "deferred" | "immediate" | "none";
|
|
373
|
+
reason?: DataTableSelectionChangeReason;
|
|
374
|
+
},
|
|
375
|
+
) => {
|
|
376
|
+
if (selection.kind === "none") return;
|
|
201
377
|
if (index < 0 || index >= tableProps.items.length) return;
|
|
202
378
|
const item = tableProps.items[index]!;
|
|
203
379
|
if (isNavigable && !isNavigable(item, index)) return;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
380
|
+
effectiveSelectedIndexRef.current = index;
|
|
381
|
+
setCursorIndex(index);
|
|
382
|
+
const reason = options.reason ?? (options.commit === "deferred"
|
|
383
|
+
? "keyboard"
|
|
384
|
+
: options.commit === "immediate"
|
|
385
|
+
? "pointer"
|
|
386
|
+
: "keyboard");
|
|
387
|
+
onCursorChange?.(item, index, reason);
|
|
388
|
+
if (options.commit === "deferred") {
|
|
389
|
+
requestSelectionScroll(index);
|
|
207
390
|
}
|
|
208
|
-
|
|
209
|
-
|
|
391
|
+
if (options.commit === "immediate") {
|
|
392
|
+
commitIndexImmediately(index, reason);
|
|
393
|
+
} else if (options.commit === "deferred") {
|
|
394
|
+
scheduleCommitIndex(index);
|
|
395
|
+
}
|
|
396
|
+
}, [
|
|
397
|
+
commitIndexImmediately,
|
|
398
|
+
isNavigable,
|
|
399
|
+
onCursorChange,
|
|
400
|
+
requestSelectionScroll,
|
|
401
|
+
scheduleCommitIndex,
|
|
402
|
+
selection.kind,
|
|
403
|
+
tableProps.items,
|
|
404
|
+
]);
|
|
405
|
+
|
|
406
|
+
const activateIndex = useCallback((index: number) => {
|
|
407
|
+
if (index < 0 || index >= tableProps.items.length) return;
|
|
408
|
+
const item = tableProps.items[index]!;
|
|
409
|
+
if (isNavigable && !isNavigable(item, index)) return;
|
|
410
|
+
updateCursorIndex(index, { commit: "none", reason: "activation" });
|
|
411
|
+
commitIndexImmediately(index, "activation");
|
|
412
|
+
onActivate?.(item, index);
|
|
413
|
+
}, [commitIndexImmediately, isNavigable, onActivate, tableProps.items, updateCursorIndex]);
|
|
210
414
|
|
|
211
415
|
const selectByOffset = useCallback((offset: -1 | 1) => {
|
|
212
416
|
if (!navigableIndices) {
|
|
@@ -218,7 +422,7 @@ export function DataTableView<
|
|
|
218
422
|
Math.min(selectedIndex + offset, tableProps.items.length - 1),
|
|
219
423
|
)
|
|
220
424
|
: 0;
|
|
221
|
-
|
|
425
|
+
updateCursorIndex(nextIndex, { commit: "deferred" });
|
|
222
426
|
return;
|
|
223
427
|
}
|
|
224
428
|
if (navigableIndices.length === 0) return;
|
|
@@ -230,8 +434,10 @@ export function DataTableView<
|
|
|
230
434
|
)
|
|
231
435
|
: 0;
|
|
232
436
|
const nextIndex = navigableIndices[nextPosition];
|
|
233
|
-
if (nextIndex !== undefined)
|
|
234
|
-
|
|
437
|
+
if (nextIndex !== undefined) {
|
|
438
|
+
updateCursorIndex(nextIndex, { commit: "deferred" });
|
|
439
|
+
}
|
|
440
|
+
}, [navigableIndices, tableProps.items.length, updateCursorIndex]);
|
|
235
441
|
|
|
236
442
|
const activateSelection = useCallback(() => {
|
|
237
443
|
if (!navigableIndices) {
|
|
@@ -250,6 +456,32 @@ export function DataTableView<
|
|
|
250
456
|
activateIndex(selectedIsNavigable ? selectedIndex : navigableIndices[0]!);
|
|
251
457
|
}, [activateIndex, navigableIndices, tableProps.items.length]);
|
|
252
458
|
|
|
459
|
+
const isItemSelected = useCallback((item: T, index: number) => (
|
|
460
|
+
index === effectiveSelectedIndex
|
|
461
|
+
&& (!isNavigable || isNavigable(item, index))
|
|
462
|
+
), [effectiveSelectedIndex, isNavigable]);
|
|
463
|
+
|
|
464
|
+
const handleTableSelect = useCallback((_item: T, index: number) => {
|
|
465
|
+
updateCursorIndex(index, { commit: "immediate" });
|
|
466
|
+
}, [updateCursorIndex]);
|
|
467
|
+
|
|
468
|
+
const handleTableActivate = useCallback((_item: T, index: number) => {
|
|
469
|
+
activateIndex(index);
|
|
470
|
+
}, [activateIndex]);
|
|
471
|
+
|
|
472
|
+
const handleRowMouseDown = useCallback((item: T, index: number, event: any) => {
|
|
473
|
+
const handled = tableProps.onRowMouseDown?.(item, index, event);
|
|
474
|
+
if (handled === true) {
|
|
475
|
+
updateCursorIndex(index, { commit: "immediate" });
|
|
476
|
+
}
|
|
477
|
+
return handled;
|
|
478
|
+
}, [tableProps.onRowMouseDown, updateCursorIndex]);
|
|
479
|
+
|
|
480
|
+
const handleRowContextMenu = useCallback((item: T, index: number, event: any) => {
|
|
481
|
+
updateCursorIndex(index, { commit: "immediate" });
|
|
482
|
+
tableProps.onRowContextMenu?.(item, index, event);
|
|
483
|
+
}, [tableProps.onRowContextMenu, updateCursorIndex]);
|
|
484
|
+
|
|
253
485
|
useShortcut((event) => {
|
|
254
486
|
if (event.defaultPrevented || event.propagationStopped) return;
|
|
255
487
|
if (!focused || !keyboardNavigation) return;
|
|
@@ -293,13 +525,11 @@ export function DataTableView<
|
|
|
293
525
|
setHoveredIdx={effectiveSetHoveredIdx}
|
|
294
526
|
scrollToIndex={effectiveScrollToIndex}
|
|
295
527
|
scrollToIndexVersion={scrollToIndexVersion + selectionScrollVersion}
|
|
296
|
-
isSelected={
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
)
|
|
302
|
-
)}
|
|
528
|
+
isSelected={isItemSelected}
|
|
529
|
+
onSelect={handleTableSelect}
|
|
530
|
+
onActivate={handleTableActivate}
|
|
531
|
+
onRowMouseDown={handleRowMouseDown}
|
|
532
|
+
onRowContextMenu={handleRowContextMenu}
|
|
303
533
|
/>
|
|
304
534
|
</TableViewFrame>
|
|
305
535
|
);
|
|
@@ -327,9 +327,12 @@ export function FeedDataTableStackView({
|
|
|
327
327
|
onBack={() => setOpenItemId(null)}
|
|
328
328
|
detailContent={detailContent}
|
|
329
329
|
detailTitle={openItem ? openItem.detailTitle ?? openItem.title : undefined}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
330
|
+
selection={{
|
|
331
|
+
kind: "index",
|
|
332
|
+
selectedIndex: activeRowIndex,
|
|
333
|
+
onChange: (_index, row) => onSelect(row.itemIndex),
|
|
334
|
+
}}
|
|
335
|
+
onActivate={(row) => openRow(row)}
|
|
333
336
|
rootBefore={rootBefore}
|
|
334
337
|
rootAfter={rootAfter}
|
|
335
338
|
rootWidth={width}
|
|
@@ -345,11 +348,6 @@ export function FeedDataTableStackView({
|
|
|
345
348
|
nextSortPreference(current, columnId as DetailColumnId)
|
|
346
349
|
)}
|
|
347
350
|
getItemKey={(row) => row.item.id}
|
|
348
|
-
isSelected={(row, index) =>
|
|
349
|
-
row.itemIndex === selectedIdx || (selectedIdx < 0 && index === 0)
|
|
350
|
-
}
|
|
351
|
-
onSelect={(row) => onSelect(row.itemIndex)}
|
|
352
|
-
onActivate={openRow}
|
|
353
351
|
renderCell={renderCell}
|
|
354
352
|
emptyStateTitle={emptyStateTitle}
|
|
355
353
|
emptyStateHint={emptyStateHint}
|
|
@@ -122,6 +122,12 @@ describe("PaneFooterBar", () => {
|
|
|
122
122
|
const col = line.indexOf("[r]efresh");
|
|
123
123
|
expect(col).toBeGreaterThanOrEqual(0);
|
|
124
124
|
|
|
125
|
+
await act(async () => {
|
|
126
|
+
await testSetup!.mockMouse.release(col + 1, 0);
|
|
127
|
+
await testSetup!.renderOnce();
|
|
128
|
+
});
|
|
129
|
+
expect(refreshCount).toBe(0);
|
|
130
|
+
|
|
125
131
|
await act(async () => {
|
|
126
132
|
await testSetup!.mockMouse.click(col + 1, 0);
|
|
127
133
|
await testSetup!.renderOnce();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Box, Span, Text, TextAttributes, useUiCapabilities } from "../../../../ui";
|
|
2
|
+
import { useRef } from "react";
|
|
2
3
|
import { colors, blendHex } from "../../../../theme/colors";
|
|
3
4
|
import { getShortcutHintWidth, ShortcutHint } from "../../../ui/shortcut-hint";
|
|
4
5
|
import {
|
|
@@ -50,13 +51,24 @@ function stopMouseEvent(event?: { stopPropagation?: () => void; preventDefault?:
|
|
|
50
51
|
function SegmentView({ segment }: { segment: PaneFooterSegment }) {
|
|
51
52
|
const interactive = !!segment.onPress && !segment.disabled;
|
|
52
53
|
const attributes = segment.parts.some((part) => part.bold) || interactive ? TextAttributes.BOLD : 0;
|
|
54
|
+
const triggerMouseDownRef = useRef(false);
|
|
55
|
+
const startSegmentPress = (event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
|
|
56
|
+
triggerMouseDownRef.current = true;
|
|
57
|
+
stopMouseEvent(event);
|
|
58
|
+
};
|
|
59
|
+
const finishSegmentPress = (event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
|
|
60
|
+
const startedOnTrigger = triggerMouseDownRef.current;
|
|
61
|
+
triggerMouseDownRef.current = false;
|
|
62
|
+
if (startedOnTrigger) segment.onPress?.();
|
|
63
|
+
else stopMouseEvent(event);
|
|
64
|
+
};
|
|
53
65
|
|
|
54
66
|
return (
|
|
55
67
|
<Text
|
|
56
68
|
fg={segment.disabled ? colors.textMuted : colors.textDim}
|
|
57
69
|
attributes={attributes}
|
|
58
|
-
onMouseDown={interactive ?
|
|
59
|
-
onMouseUp={interactive ?
|
|
70
|
+
onMouseDown={interactive ? startSegmentPress : undefined}
|
|
71
|
+
onMouseUp={interactive ? finishSegmentPress : undefined}
|
|
60
72
|
{...(interactive ? { "data-gloom-interactive": "true" } : {})}
|
|
61
73
|
>
|
|
62
74
|
{segment.parts.map((part, index) => (
|
|
@@ -11,7 +11,11 @@ export function getPaneDisplayTitle(
|
|
|
11
11
|
const channelId = typeof instance.settings?.channelId === "string" && instance.settings.channelId.trim()
|
|
12
12
|
? instance.settings.channelId.trim()
|
|
13
13
|
: "everyone";
|
|
14
|
-
|
|
14
|
+
const title = typeof instance.title === "string" ? instance.title.trim() : "";
|
|
15
|
+
const displayTitle = title && !title.startsWith("dm:") && !title.startsWith("group:") ? title : undefined;
|
|
16
|
+
if (channelId.startsWith("dm:")) return displayTitle ?? "DM";
|
|
17
|
+
if (channelId.startsWith("group:")) return displayTitle ?? "Group";
|
|
18
|
+
return displayTitle ?? `#${channelId}`;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
if (instance.paneId === TICKER_RESEARCH_PANE_ID) {
|