drexler 0.2.12 → 0.2.13
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/ui/App.tsx +11 -11
- package/src/ui/CommandPalette.tsx +23 -13
- package/src/ui/DealDeskHeader.tsx +4 -9
- package/src/ui/MascotIntro.tsx +20 -26
- package/src/ui/Spinner.tsx +11 -9
- package/src/ui/StatusBar.tsx +11 -43
- package/src/ui/TranscriptViewport.tsx +13 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.13
|
|
4
|
+
|
|
5
|
+
- Hardened startup panel layout across narrow, standard, and wide terminals.
|
|
6
|
+
- Clamped the embedded Deal Desk to its actual startup-panel column.
|
|
7
|
+
- Improved display-width clipping for Deal Desk, command palette, spinner, status bar, and transcript row budgeting.
|
|
8
|
+
- Added regression coverage for duplicate startup chrome, wide glyphs, long command rows, and short-terminal startup suppression.
|
|
9
|
+
|
|
3
10
|
## 0.2.12
|
|
4
11
|
|
|
5
12
|
- Removed the duplicate startup card render so normal launches show one startup panel.
|
package/package.json
CHANGED
package/src/ui/App.tsx
CHANGED
|
@@ -136,14 +136,17 @@ export function App({
|
|
|
136
136
|
const chromeWidth = useMemo(() => Math.max(1, cols), [cols]);
|
|
137
137
|
const statusBarWidth = inputWidth;
|
|
138
138
|
const isCompact = mode === "very-narrow";
|
|
139
|
-
const integratedIntro =
|
|
139
|
+
const integratedIntro =
|
|
140
|
+
showIntroChrome && typeof greeting === "string" && rows >= 32;
|
|
141
|
+
const introRowBudget =
|
|
142
|
+
integratedIntro ? (chromeWidth >= 112 ? 14 : chromeWidth >= 72 ? 26 : 6) : 0;
|
|
140
143
|
const maxTranscriptRows = useMemo(
|
|
141
144
|
() =>
|
|
142
145
|
Math.max(
|
|
143
146
|
1,
|
|
144
|
-
transcriptRowsForTerminalRows(rows) -
|
|
147
|
+
transcriptRowsForTerminalRows(rows) - introRowBudget,
|
|
145
148
|
),
|
|
146
|
-
[
|
|
149
|
+
[introRowBudget, rows],
|
|
147
150
|
);
|
|
148
151
|
|
|
149
152
|
const [items, setItems] = useState<ChatItem[]>([]);
|
|
@@ -716,11 +719,7 @@ export function App({
|
|
|
716
719
|
const isBusy =
|
|
717
720
|
requestInFlight || streaming !== null || thinking !== null || synergyEvent !== null;
|
|
718
721
|
const headerStatus = isBusy ? "streaming" : deskStatus;
|
|
719
|
-
const
|
|
720
|
-
chromeWidth >= 112
|
|
721
|
-
? Math.min(72, Math.max(42, Math.floor(chromeWidth * 0.34)))
|
|
722
|
-
: Math.max(32, chromeWidth - 8);
|
|
723
|
-
const dealDeskHeader = (
|
|
722
|
+
const renderDealDeskHeader = (width: number) => (
|
|
724
723
|
<DealDeskHeader
|
|
725
724
|
model={model}
|
|
726
725
|
mood={mood}
|
|
@@ -732,10 +731,11 @@ export function App({
|
|
|
732
731
|
status={headerStatus}
|
|
733
732
|
compact={isCompact}
|
|
734
733
|
notice={!integratedIntro ? deskNotice ?? undefined : undefined}
|
|
735
|
-
maxWidth={integratedIntro ?
|
|
734
|
+
maxWidth={integratedIntro ? Math.min(72, Math.max(1, width)) : width}
|
|
736
735
|
marginBottom={integratedIntro ? 0 : 1}
|
|
737
736
|
/>
|
|
738
737
|
);
|
|
738
|
+
const dealDeskHeader = renderDealDeskHeader(chromeWidth);
|
|
739
739
|
const visibleTranscriptRows = synergyEvent
|
|
740
740
|
? Math.max(1, maxTranscriptRows - synergyEventRows(chromeWidth, isCompact))
|
|
741
741
|
: maxTranscriptRows;
|
|
@@ -748,7 +748,7 @@ export function App({
|
|
|
748
748
|
<MascotDashboard
|
|
749
749
|
greeting={greeting}
|
|
750
750
|
width={chromeWidth}
|
|
751
|
-
dealDesk={
|
|
751
|
+
dealDesk={renderDealDeskHeader}
|
|
752
752
|
/>
|
|
753
753
|
</Box>
|
|
754
754
|
) : (
|
|
@@ -769,7 +769,7 @@ export function App({
|
|
|
769
769
|
</Box>
|
|
770
770
|
)}
|
|
771
771
|
{thinking !== null && streaming === null && (
|
|
772
|
-
<Box
|
|
772
|
+
<Box marginBottom={1}>
|
|
773
773
|
<Spinner label={thinking} width={chromeWidth} />
|
|
774
774
|
</Box>
|
|
775
775
|
)}
|
|
@@ -54,13 +54,18 @@ function paletteHeading(items: ReadonlyArray<SlashCommand>): {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function padDisplayText(input: string, width: number): string {
|
|
58
|
+
const clipped = fitDisplayText(input, width);
|
|
59
|
+
return `${clipped}${" ".repeat(Math.max(0, width - displayWidth(clipped)))}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
function CommandPaletteInner({ items, selectedIdx, width = 80 }: Props) {
|
|
58
63
|
const t = useTheme();
|
|
59
64
|
const safeWidth = Math.max(1, Math.floor(width));
|
|
60
65
|
const tiny = safeWidth < 26;
|
|
61
66
|
const heading = useMemo(() => paletteHeading(items), [items]);
|
|
62
67
|
const maxNameW = useMemo(
|
|
63
|
-
() => items.reduce((m, i) => Math.max(m, i.name
|
|
68
|
+
() => items.reduce((m, i) => Math.max(m, displayWidth(i.name)), 0),
|
|
64
69
|
[items],
|
|
65
70
|
);
|
|
66
71
|
if (items.length === 0) return null;
|
|
@@ -87,10 +92,21 @@ function CommandPaletteInner({ items, selectedIdx, width = 80 }: Props) {
|
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
const innerWidth = Math.max(1, safeWidth - 4);
|
|
90
|
-
const
|
|
95
|
+
const markerWidth = 2;
|
|
96
|
+
const nameBudget = Math.max(
|
|
97
|
+
6,
|
|
98
|
+
Math.min(maxNameW + 1, Math.floor(innerWidth * 0.44)),
|
|
99
|
+
);
|
|
100
|
+
const descBudget = Math.max(
|
|
101
|
+
6,
|
|
102
|
+
Math.min(
|
|
103
|
+
Math.floor(innerWidth * 0.34),
|
|
104
|
+
innerWidth - markerWidth - nameBudget - 1,
|
|
105
|
+
),
|
|
106
|
+
);
|
|
91
107
|
const hintBudget = Math.max(
|
|
92
108
|
0,
|
|
93
|
-
innerWidth -
|
|
109
|
+
innerWidth - markerWidth - nameBudget - 1 - descBudget - 2,
|
|
94
110
|
);
|
|
95
111
|
|
|
96
112
|
return (
|
|
@@ -121,18 +137,12 @@ function CommandPaletteInner({ items, selectedIdx, width = 80 }: Props) {
|
|
|
121
137
|
const hint =
|
|
122
138
|
item.hint ??
|
|
123
139
|
(isArgumentSuggestion ? "" : COMMAND_HINTS[item.name] ?? item.description);
|
|
124
|
-
const name = item.name
|
|
125
|
-
const desc =
|
|
140
|
+
const name = padDisplayText(item.name, nameBudget);
|
|
141
|
+
const desc = padDisplayText(item.description, descBudget);
|
|
126
142
|
const clippedHint =
|
|
127
143
|
hintBudget > 0 ? fitDisplayText(hint, hintBudget) : "";
|
|
128
|
-
const rowWidth =
|
|
129
|
-
2 +
|
|
130
|
-
displayWidth(name) +
|
|
131
|
-
1 +
|
|
132
|
-
displayWidth(desc) +
|
|
133
|
-
(clippedHint ? 2 + displayWidth(clippedHint) : 0);
|
|
134
144
|
return (
|
|
135
|
-
<Box key={item.name} width={
|
|
145
|
+
<Box key={item.name} width={innerWidth} flexShrink={1}>
|
|
136
146
|
<Text color={sel ? t.primaryLight : t.primaryDim} bold={sel}>
|
|
137
147
|
{sel ? "› " : " "}
|
|
138
148
|
</Text>
|
|
@@ -140,7 +150,7 @@ function CommandPaletteInner({ items, selectedIdx, width = 80 }: Props) {
|
|
|
140
150
|
{name}
|
|
141
151
|
</Text>
|
|
142
152
|
<Text color={t.primaryDim}> </Text>
|
|
143
|
-
<Text color={sel ? t.text : t.dim}
|
|
153
|
+
<Text color={sel ? t.text : t.dim}>
|
|
144
154
|
{desc}
|
|
145
155
|
</Text>
|
|
146
156
|
{clippedHint ? (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Box, Text } from "ink";
|
|
2
2
|
import { memo, useMemo } from "react";
|
|
3
|
+
import { displayWidth, fitDisplayText } from "./graphemes.ts";
|
|
3
4
|
import { useTheme } from "./ThemeContext.tsx";
|
|
4
5
|
|
|
5
6
|
export type DealDeskHeaderStatus = "idle" | "streaming" | "error";
|
|
@@ -29,25 +30,19 @@ const STATUS_LABEL: Record<DealDeskHeaderStatus, string> = {
|
|
|
29
30
|
error: "ERROR",
|
|
30
31
|
};
|
|
31
32
|
|
|
32
|
-
function visibleLength(input: string): number {
|
|
33
|
-
return Array.from(input).length;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
33
|
function clampText(input: string, max: number): string {
|
|
37
34
|
if (max <= 0) return "";
|
|
38
|
-
|
|
39
|
-
if (max === 1) return "…";
|
|
40
|
-
return `${Array.from(input).slice(0, max - 1).join("")}…`;
|
|
35
|
+
return fitDisplayText(input, max);
|
|
41
36
|
}
|
|
42
37
|
|
|
43
38
|
function padToWidth(input: string, width: number): string {
|
|
44
|
-
const len =
|
|
39
|
+
const len = displayWidth(input);
|
|
45
40
|
if (len >= width) return input;
|
|
46
41
|
return `${input}${" ".repeat(width - len)}`;
|
|
47
42
|
}
|
|
48
43
|
|
|
49
44
|
function shellLine(left: string, right: string, width: number): string {
|
|
50
|
-
const available = Math.max(0, width -
|
|
45
|
+
const available = Math.max(0, width - displayWidth(left) - displayWidth(right));
|
|
51
46
|
return `${left}${"─".repeat(available)}${right}`;
|
|
52
47
|
}
|
|
53
48
|
|
package/src/ui/MascotIntro.tsx
CHANGED
|
@@ -127,13 +127,8 @@ const COMPACT_DELAY_MS = 850;
|
|
|
127
127
|
const SETTLE_HOLD_MS = 1200;
|
|
128
128
|
const FRAME_CHROME_WIDTH = 4;
|
|
129
129
|
const GUTTER_WIDTH = 4;
|
|
130
|
-
const SPLIT_DIVIDER_WIDTH = 3;
|
|
131
130
|
const BOOT_BAR_WIDTH = MASCOT_WIDTH - 1;
|
|
132
|
-
const
|
|
133
|
-
const SPLIT_DIVIDER_ROWS: number[] = Array.from(
|
|
134
|
-
{ length: SPLIT_DIVIDER_HEIGHT },
|
|
135
|
-
(_, i) => i,
|
|
136
|
-
);
|
|
131
|
+
const RIGHT_COLUMN_BORDER_WIDTH = 2;
|
|
137
132
|
|
|
138
133
|
interface IntroProps {
|
|
139
134
|
greeting: string;
|
|
@@ -146,7 +141,7 @@ interface MascotDashboardProps {
|
|
|
146
141
|
bar?: string;
|
|
147
142
|
barColor?: string;
|
|
148
143
|
mascotStatus?: string;
|
|
149
|
-
dealDesk?: ReactNode;
|
|
144
|
+
dealDesk?: (width: number) => ReactNode;
|
|
150
145
|
}
|
|
151
146
|
|
|
152
147
|
function bootBar(frameIdx: number, total: number): string {
|
|
@@ -191,7 +186,7 @@ export function MascotDashboard({
|
|
|
191
186
|
const tinyTerminal = width < 21;
|
|
192
187
|
const compact = width < 72;
|
|
193
188
|
const sideBySide = width >= 112;
|
|
194
|
-
const available = compact ? Math.max(1, width - 1) : Math.max(28, width);
|
|
189
|
+
const available = compact ? Math.max(1, width - 1) : Math.max(28, width - 1);
|
|
195
190
|
const innerWidth = compact
|
|
196
191
|
? available
|
|
197
192
|
: Math.max(24, available - FRAME_CHROME_WIDTH);
|
|
@@ -200,11 +195,17 @@ export function MascotDashboard({
|
|
|
200
195
|
: sideBySide
|
|
201
196
|
? Math.max(
|
|
202
197
|
MASCOT_WIDTH + GUTTER_WIDTH + 24,
|
|
203
|
-
Math.floor((innerWidth -
|
|
198
|
+
Math.floor((innerWidth - RIGHT_COLUMN_BORDER_WIDTH) / 2),
|
|
204
199
|
)
|
|
205
200
|
: innerWidth;
|
|
201
|
+
const rightColumnWidth = sideBySide
|
|
202
|
+
? Math.max(20, innerWidth - leftPanelWidth)
|
|
203
|
+
: innerWidth;
|
|
204
|
+
const rightInnerWidth = sideBySide
|
|
205
|
+
? Math.max(1, rightColumnWidth - RIGHT_COLUMN_BORDER_WIDTH)
|
|
206
|
+
: rightColumnWidth;
|
|
206
207
|
const tipsWidth = sideBySide
|
|
207
|
-
?
|
|
208
|
+
? rightInnerWidth
|
|
208
209
|
: innerWidth;
|
|
209
210
|
const copyWidth = compact
|
|
210
211
|
? available
|
|
@@ -220,7 +221,7 @@ export function MascotDashboard({
|
|
|
220
221
|
Drexler™
|
|
221
222
|
</Text>
|
|
222
223
|
<Text color={t.primaryLight}>{greeting}</Text>
|
|
223
|
-
{dealDesk ? <Box marginTop={1}>{dealDesk}</Box> : null}
|
|
224
|
+
{dealDesk ? <Box marginTop={1}>{dealDesk(Math.max(1, available))}</Box> : null}
|
|
224
225
|
</Box>
|
|
225
226
|
);
|
|
226
227
|
}
|
|
@@ -234,7 +235,7 @@ export function MascotDashboard({
|
|
|
234
235
|
Drexler International™
|
|
235
236
|
</Text>
|
|
236
237
|
<Text color={t.primaryLight}>{greeting}</Text>
|
|
237
|
-
{dealDesk ? <Box marginTop={1}>{dealDesk}</Box> : null}
|
|
238
|
+
{dealDesk ? <Box marginTop={1}>{dealDesk(Math.max(1, available))}</Box> : null}
|
|
238
239
|
</Box>
|
|
239
240
|
);
|
|
240
241
|
}
|
|
@@ -275,27 +276,20 @@ export function MascotDashboard({
|
|
|
275
276
|
</Box>
|
|
276
277
|
</Box>
|
|
277
278
|
{sideBySide ? (
|
|
278
|
-
<>
|
|
279
|
-
<Box flexDirection="column" width={SPLIT_DIVIDER_WIDTH}>
|
|
280
|
-
{SPLIT_DIVIDER_ROWS.map((idx) => (
|
|
281
|
-
<Text key={idx} color={t.primaryDim}>
|
|
282
|
-
{" │ "}
|
|
283
|
-
</Text>
|
|
284
|
-
))}
|
|
285
|
-
</Box>
|
|
286
279
|
<Box
|
|
287
280
|
flexDirection="column"
|
|
288
|
-
width={
|
|
289
|
-
|
|
281
|
+
width={rightColumnWidth}
|
|
282
|
+
borderLeft
|
|
283
|
+
borderColor={t.primaryDim}
|
|
284
|
+
paddingLeft={1}
|
|
290
285
|
>
|
|
291
|
-
<TipsPanel width={
|
|
292
|
-
{dealDesk ? <Box marginTop={1}>{dealDesk}</Box> : null}
|
|
286
|
+
<TipsPanel width={rightInnerWidth} />
|
|
287
|
+
{dealDesk ? <Box marginTop={1}>{dealDesk(rightInnerWidth)}</Box> : null}
|
|
293
288
|
</Box>
|
|
294
|
-
</>
|
|
295
289
|
) : (
|
|
296
290
|
<Box marginTop={1} width={tipsWidth} flexDirection="column">
|
|
297
291
|
<TipsPanel width={tipsWidth} />
|
|
298
|
-
{dealDesk ? <Box marginTop={1}>{dealDesk}</Box> : null}
|
|
292
|
+
{dealDesk ? <Box marginTop={1}>{dealDesk(tipsWidth)}</Box> : null}
|
|
299
293
|
</Box>
|
|
300
294
|
)}
|
|
301
295
|
</Box>
|
package/src/ui/Spinner.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Box, Text } from "ink";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import { fitDisplayText } from "./graphemes.ts";
|
|
3
|
+
import { displayWidth, fitDisplayText } from "./graphemes.ts";
|
|
4
4
|
import { useTheme } from "./ThemeContext.tsx";
|
|
5
5
|
|
|
6
6
|
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -47,8 +47,15 @@ export function Spinner({ label, width = 80 }: Props) {
|
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const
|
|
50
|
+
const innerWidth = Math.max(1, safeWidth - 4);
|
|
51
51
|
const showStage = safeWidth >= 42;
|
|
52
|
+
const stageLabel = showStage ? ` · ${stage}` : "";
|
|
53
|
+
const secondsLabel = seconds > 0 && safeWidth >= 34 ? ` · ${seconds}s` : "";
|
|
54
|
+
const fixedWidth =
|
|
55
|
+
displayWidth(`${FRAMES[i]} WORKING ─ `) +
|
|
56
|
+
displayWidth(stageLabel) +
|
|
57
|
+
displayWidth(secondsLabel);
|
|
58
|
+
const labelBudget = Math.max(1, innerWidth - fixedWidth);
|
|
52
59
|
|
|
53
60
|
return (
|
|
54
61
|
<Box
|
|
@@ -66,13 +73,8 @@ export function Spinner({ label, width = 80 }: Props) {
|
|
|
66
73
|
<Text color={t.text} wrap="truncate">
|
|
67
74
|
{fitDisplayText(label, labelBudget)}
|
|
68
75
|
</Text>
|
|
69
|
-
{
|
|
70
|
-
{
|
|
71
|
-
<>
|
|
72
|
-
<Text color={t.primaryDim}> · </Text>
|
|
73
|
-
<Text color={t.dim}>{seconds}s</Text>
|
|
74
|
-
</>
|
|
75
|
-
) : null}
|
|
76
|
+
{stageLabel ? <Text color={t.dim}>{stageLabel}</Text> : null}
|
|
77
|
+
{secondsLabel ? <Text color={t.dim}>{secondsLabel}</Text> : null}
|
|
76
78
|
</Box>
|
|
77
79
|
);
|
|
78
80
|
}
|
package/src/ui/StatusBar.tsx
CHANGED
|
@@ -16,13 +16,6 @@ interface Props {
|
|
|
16
16
|
|
|
17
17
|
const MAX_WITTICISM_LEN = 60;
|
|
18
18
|
|
|
19
|
-
function clampText(input: string, max: number): string {
|
|
20
|
-
if (input.length <= max) return input;
|
|
21
|
-
if (max <= 0) return "";
|
|
22
|
-
if (max === 1) return "…";
|
|
23
|
-
return input.slice(0, max - 1) + "…";
|
|
24
|
-
}
|
|
25
|
-
|
|
26
19
|
function StatusBarInner({
|
|
27
20
|
messageCount,
|
|
28
21
|
witticism,
|
|
@@ -40,52 +33,27 @@ function StatusBarInner({
|
|
|
40
33
|
}),
|
|
41
34
|
[t.primaryLight, t.warning, t.error],
|
|
42
35
|
);
|
|
36
|
+
const safeWidth =
|
|
37
|
+
typeof maxWidth === "number" ? Math.max(1, Math.floor(maxWidth)) : undefined;
|
|
43
38
|
const countLabel = `${messageCount} message${messageCount === 1 ? "" : "s"}`;
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
maxWidth -
|
|
50
|
-
"● ".length -
|
|
51
|
-
countLabel.length -
|
|
52
|
-
hintLabel.length -
|
|
53
|
-
" │ ".length -
|
|
54
|
-
2,
|
|
55
|
-
)
|
|
56
|
-
: MAX_WITTICISM_LEN;
|
|
57
|
-
const safe = clampText(witticism, Math.min(MAX_WITTICISM_LEN, quoteWidth));
|
|
39
|
+
const quote = `"${fitDisplayText(witticism, MAX_WITTICISM_LEN)}"`;
|
|
40
|
+
const line = compact
|
|
41
|
+
? `${countLabel}${scrollHint ? ` │ ${scrollHint}` : ""}`
|
|
42
|
+
: `${countLabel}${scrollHint ? ` │ ${scrollHint}` : ""} │ ${quote}`;
|
|
43
|
+
const body = fitDisplayText(line, Math.max(1, (safeWidth ?? 80) - 2));
|
|
58
44
|
const box = compact ? (
|
|
59
45
|
<Box>
|
|
60
46
|
<Text color={dotColor[status]}>● </Text>
|
|
61
|
-
<Text color={t.dim}>{
|
|
62
|
-
{scrollHint ? (
|
|
63
|
-
<>
|
|
64
|
-
<Text color={t.primaryDim}>{" │ "}</Text>
|
|
65
|
-
<Text color={t.primaryLight}>
|
|
66
|
-
{fitDisplayText(scrollHint, Math.max(1, maxWidth ?? 24))}
|
|
67
|
-
</Text>
|
|
68
|
-
</>
|
|
69
|
-
) : null}
|
|
47
|
+
<Text color={t.dim} wrap="truncate">{body}</Text>
|
|
70
48
|
</Box>
|
|
71
49
|
) : (
|
|
72
50
|
<Box>
|
|
73
51
|
<Text color={dotColor[status]}>● </Text>
|
|
74
|
-
<Text color={t.dim}>{
|
|
75
|
-
{scrollHint ? (
|
|
76
|
-
<>
|
|
77
|
-
<Text color={t.primaryDim}>{" │ "}</Text>
|
|
78
|
-
<Text color={t.primaryLight}>{scrollHint}</Text>
|
|
79
|
-
</>
|
|
80
|
-
) : null}
|
|
81
|
-
<Text color={t.primaryDim}>{" │ "}</Text>
|
|
82
|
-
<Text color={t.dim} italic>
|
|
83
|
-
"{safe}"
|
|
84
|
-
</Text>
|
|
52
|
+
<Text color={t.dim} italic wrap="truncate">{body}</Text>
|
|
85
53
|
</Box>
|
|
86
54
|
);
|
|
87
|
-
if (typeof
|
|
88
|
-
return <Box width={
|
|
55
|
+
if (typeof safeWidth === "number") {
|
|
56
|
+
return <Box width={safeWidth}>{box}</Box>;
|
|
89
57
|
}
|
|
90
58
|
return box;
|
|
91
59
|
}
|
|
@@ -275,11 +275,19 @@ function selectWindow(
|
|
|
275
275
|
reserveTop = nextReserveTop;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
const visible = entries.slice(start, end);
|
|
279
|
+
const visibleRows = visible.reduce(
|
|
280
|
+
(sum, entry) => sum + Math.max(1, entry.estimatedRows),
|
|
281
|
+
0,
|
|
282
|
+
);
|
|
283
|
+
let hiddenBefore = start;
|
|
284
|
+
let hiddenAfter = entries.length - end;
|
|
285
|
+
if (visibleRows + (hiddenBefore > 0 ? 1 : 0) + (hiddenAfter > 0 ? 1 : 0) > safeRows) {
|
|
286
|
+
if (hiddenBefore > 0) hiddenBefore = 0;
|
|
287
|
+
if (visibleRows + (hiddenAfter > 0 ? 1 : 0) > safeRows) hiddenAfter = 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return { visible, hiddenBefore, hiddenAfter };
|
|
283
291
|
}
|
|
284
292
|
|
|
285
293
|
function ScrollIndicator({
|