tekivex-ui 2.5.2 → 2.5.4
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/dist/TkxForm-DSMDo2Ol.js +388 -0
- package/dist/TkxForm-DwV_n-eE.cjs +12 -0
- package/dist/charts.cjs +1 -1
- package/dist/charts.js +324 -301
- package/dist/headless.cjs +1 -1
- package/dist/headless.js +396 -195
- package/dist/i18n.cjs +1 -0
- package/dist/i18n.js +757 -0
- package/dist/index.cjs +27 -22
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4994 -5089
- package/dist/quantum.cjs +91 -0
- package/dist/quantum.js +2312 -0
- package/dist/realtime.cjs +25 -0
- package/dist/realtime.js +944 -0
- package/dist/security-C369oOWH.cjs +1 -0
- package/dist/security-CzAmaMqa.js +97 -0
- package/dist/src/charts/TkxDonutChart.d.ts.map +1 -1
- package/dist/src/charts/TkxPieChart.d.ts.map +1 -1
- package/dist/src/components/TkxDataGrid.d.ts +9 -1
- package/dist/src/components/TkxDataGrid.d.ts.map +1 -1
- package/dist/src/components/TkxDropdown.d.ts +41 -0
- package/dist/src/components/TkxDropdown.d.ts.map +1 -0
- package/dist/src/components/TkxLiveFeed.d.ts +891 -0
- package/dist/src/components/TkxLiveFeed.d.ts.map +1 -0
- package/dist/src/components/TkxLiveLog.d.ts +892 -0
- package/dist/src/components/TkxLiveLog.d.ts.map +1 -0
- package/dist/src/components/TkxLiveMetrics.d.ts +24 -0
- package/dist/src/components/TkxLiveMetrics.d.ts.map +1 -0
- package/dist/src/components/TkxPlayground.d.ts +15 -0
- package/dist/src/components/TkxPlayground.d.ts.map +1 -0
- package/dist/src/components/TkxQuantumForm.d.ts +20 -0
- package/dist/src/components/TkxQuantumForm.d.ts.map +1 -0
- package/dist/src/components/TkxRealTimeChart.d.ts +27 -0
- package/dist/src/components/TkxRealTimeChart.d.ts.map +1 -0
- package/dist/src/components/TkxTable.d.ts +11 -1
- package/dist/src/components/TkxTable.d.ts.map +1 -1
- package/dist/src/components/TkxThemeBuilder.d.ts +9 -0
- package/dist/src/components/TkxThemeBuilder.d.ts.map +1 -0
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/components/index.d.ts.map +1 -1
- package/dist/src/engine/index.d.ts +2 -0
- package/dist/src/engine/index.d.ts.map +1 -1
- package/dist/src/engine/quantum-ai.d.ts +201 -0
- package/dist/src/engine/quantum-ai.d.ts.map +1 -0
- package/dist/src/engine/tkx.d.ts.map +1 -1
- package/dist/src/headless/index.d.ts +6 -0
- package/dist/src/headless/index.d.ts.map +1 -1
- package/dist/src/headless/useInfiniteQuery.d.ts +35 -0
- package/dist/src/headless/useInfiniteQuery.d.ts.map +1 -0
- package/dist/src/headless/useSSE.d.ts +36 -0
- package/dist/src/headless/useSSE.d.ts.map +1 -0
- package/dist/src/headless/useWebSocket.d.ts +33 -0
- package/dist/src/headless/useWebSocket.d.ts.map +1 -0
- package/dist/src/quantum/index.d.ts +9 -0
- package/dist/src/quantum/index.d.ts.map +1 -0
- package/dist/src/realtime/index.d.ts +9 -0
- package/dist/src/realtime/index.d.ts.map +1 -0
- package/dist/{TkxForm-BWK4LqY3.js → tkx-BJ10JtMv.js} +372 -721
- package/dist/tkx-BeNyC0w7.cjs +4 -0
- package/package.json +16 -1
- package/src/charts/TkxDonutChart.tsx +15 -2
- package/src/charts/TkxPieChart.tsx +17 -2
- package/src/components/TkxDataGrid.tsx +72 -0
- package/src/components/TkxDropdown.tsx +802 -0
- package/src/components/TkxLiveFeed.tsx +329 -0
- package/src/components/TkxLiveLog.tsx +470 -0
- package/src/components/TkxLiveMetrics.tsx +291 -0
- package/src/components/TkxPlayground.tsx +736 -0
- package/src/components/TkxQuantumForm.tsx +592 -0
- package/src/components/TkxRealTimeChart.tsx +381 -0
- package/src/components/TkxTable.tsx +351 -77
- package/src/components/TkxThemeBuilder.tsx +685 -0
- package/src/components/index.ts +4 -0
- package/src/engine/index.ts +2 -0
- package/src/engine/quantum-ai.ts +1045 -0
- package/src/engine/tkx.ts +95 -4
- package/src/headless/index.ts +6 -0
- package/src/headless/useInfiniteQuery.ts +153 -0
- package/src/headless/useSSE.ts +165 -0
- package/src/headless/useWebSocket.ts +229 -0
- package/src/quantum/index.ts +39 -0
- package/src/realtime/index.ts +21 -0
- package/dist/TkxForm-ljQjX7KD.cjs +0 -15
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useCallback,
|
|
6
|
+
useMemo,
|
|
7
|
+
createElement,
|
|
8
|
+
type CSSProperties,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { useTheme } from '../themes';
|
|
11
|
+
import { sanitizeString } from '../engine/security';
|
|
12
|
+
import { tkx } from '../engine/tkx';
|
|
13
|
+
|
|
14
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
17
|
+
|
|
18
|
+
export interface LogEntry {
|
|
19
|
+
id: string;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
level: LogLevel;
|
|
22
|
+
message: string;
|
|
23
|
+
source?: string;
|
|
24
|
+
metadata?: Record<string, string | number | boolean>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface TkxLiveLogProps {
|
|
28
|
+
entries: LogEntry[];
|
|
29
|
+
maxEntries?: number;
|
|
30
|
+
height?: number | string;
|
|
31
|
+
autoScroll?: boolean;
|
|
32
|
+
showLevel?: boolean;
|
|
33
|
+
showTimestamp?: boolean;
|
|
34
|
+
showSource?: boolean;
|
|
35
|
+
filterLevel?: LogLevel;
|
|
36
|
+
searchQuery?: string;
|
|
37
|
+
monospace?: boolean;
|
|
38
|
+
onEntryClick?: (entry: LogEntry) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
const ROW_HEIGHT = 28;
|
|
44
|
+
const OVERSCAN = 8;
|
|
45
|
+
|
|
46
|
+
const LEVEL_ORDER: Record<LogLevel, number> = {
|
|
47
|
+
debug: 0,
|
|
48
|
+
info: 1,
|
|
49
|
+
warn: 2,
|
|
50
|
+
error: 3,
|
|
51
|
+
fatal: 4,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ── CSS injection ────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
let logStylesInjected = false;
|
|
57
|
+
function injectLogStyles() {
|
|
58
|
+
if (logStylesInjected || typeof document === 'undefined') return;
|
|
59
|
+
logStylesInjected = true;
|
|
60
|
+
const el = document.createElement('style');
|
|
61
|
+
el.id = 'tkx-live-log-styles';
|
|
62
|
+
el.textContent = `
|
|
63
|
+
.tkx-log-row:hover { filter: brightness(1.12); }
|
|
64
|
+
.tkx-log-highlight { background: #ffbe0b55; border-radius: 2px; padding: 0 1px; }
|
|
65
|
+
`.trim();
|
|
66
|
+
document.head.appendChild(el);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
function formatLogTime(ts: number): string {
|
|
72
|
+
const d = new Date(ts);
|
|
73
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
74
|
+
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
75
|
+
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
76
|
+
const ms = String(d.getMilliseconds()).padStart(3, '0');
|
|
77
|
+
return `${hh}:${mm}:${ss}.${ms}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getLevelColor(level: LogLevel, theme: ReturnType<typeof useTheme>): string {
|
|
81
|
+
switch (level) {
|
|
82
|
+
case 'debug': return theme.textMuted;
|
|
83
|
+
case 'info': return theme.info;
|
|
84
|
+
case 'warn': return theme.warning;
|
|
85
|
+
case 'error': return theme.danger;
|
|
86
|
+
case 'fatal': return theme.danger;
|
|
87
|
+
default: return theme.textMuted;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getLevelBg(level: LogLevel, theme: ReturnType<typeof useTheme>): string {
|
|
92
|
+
if (level === 'fatal') return `${theme.danger}22`;
|
|
93
|
+
return 'transparent';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getLevelLabel(level: LogLevel): string {
|
|
97
|
+
return `[${level.toUpperCase().padEnd(5)}]`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Highlight matching text ──────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
function highlightText(text: string, query: string): React.ReactNode[] {
|
|
103
|
+
if (!query) return [text];
|
|
104
|
+
const safe = sanitizeString(text);
|
|
105
|
+
const safeQ = sanitizeString(query);
|
|
106
|
+
if (!safeQ) return [safe];
|
|
107
|
+
try {
|
|
108
|
+
const regex = new RegExp(`(${safeQ.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
|
109
|
+
const parts = safe.split(regex);
|
|
110
|
+
return parts.map((part, i) =>
|
|
111
|
+
regex.test(part)
|
|
112
|
+
? createElement('mark', { key: i, className: 'tkx-log-highlight' }, part)
|
|
113
|
+
: part,
|
|
114
|
+
);
|
|
115
|
+
} catch {
|
|
116
|
+
return [safe];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── LogRow ───────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
interface LogRowProps {
|
|
123
|
+
entry: LogEntry;
|
|
124
|
+
showLevel: boolean;
|
|
125
|
+
showTimestamp: boolean;
|
|
126
|
+
showSource: boolean;
|
|
127
|
+
searchQuery: string;
|
|
128
|
+
monospace: boolean;
|
|
129
|
+
isExpanded: boolean;
|
|
130
|
+
onToggle: () => void;
|
|
131
|
+
onEntryClick?: (entry: LogEntry) => void;
|
|
132
|
+
theme: ReturnType<typeof useTheme>;
|
|
133
|
+
style?: CSSProperties;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function LogRow({
|
|
137
|
+
entry,
|
|
138
|
+
showLevel,
|
|
139
|
+
showTimestamp,
|
|
140
|
+
showSource,
|
|
141
|
+
searchQuery,
|
|
142
|
+
monospace,
|
|
143
|
+
isExpanded,
|
|
144
|
+
onToggle,
|
|
145
|
+
onEntryClick,
|
|
146
|
+
theme,
|
|
147
|
+
style,
|
|
148
|
+
}: LogRowProps) {
|
|
149
|
+
const levelColor = getLevelColor(entry.level, theme);
|
|
150
|
+
const levelBg = getLevelBg(entry.level, theme);
|
|
151
|
+
const hasMetadata = entry.metadata && Object.keys(entry.metadata).length > 0;
|
|
152
|
+
|
|
153
|
+
const rowStyle: CSSProperties = {
|
|
154
|
+
...style,
|
|
155
|
+
display: 'flex',
|
|
156
|
+
alignItems: 'center',
|
|
157
|
+
gap: 6,
|
|
158
|
+
padding: '0 10px',
|
|
159
|
+
fontSize: 12,
|
|
160
|
+
fontFamily: monospace ? '"JetBrains Mono", "Fira Mono", Consolas, monospace' : 'inherit',
|
|
161
|
+
cursor: hasMetadata || onEntryClick ? 'pointer' : 'default',
|
|
162
|
+
background: levelBg,
|
|
163
|
+
boxSizing: 'border-box',
|
|
164
|
+
height: ROW_HEIGHT,
|
|
165
|
+
overflow: 'hidden',
|
|
166
|
+
whiteSpace: 'nowrap',
|
|
167
|
+
borderBottom: `1px solid ${theme.border}22`,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const handleClick = () => {
|
|
171
|
+
if (hasMetadata) onToggle();
|
|
172
|
+
if (onEntryClick) onEntryClick(entry);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return createElement(
|
|
176
|
+
'div',
|
|
177
|
+
{
|
|
178
|
+
style: rowStyle,
|
|
179
|
+
className: 'tkx-log-row',
|
|
180
|
+
onClick: handleClick,
|
|
181
|
+
role: hasMetadata ? 'button' : undefined,
|
|
182
|
+
tabIndex: hasMetadata ? 0 : undefined,
|
|
183
|
+
onKeyDown: hasMetadata
|
|
184
|
+
? (e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') onToggle(); }
|
|
185
|
+
: undefined,
|
|
186
|
+
'aria-expanded': hasMetadata ? isExpanded : undefined,
|
|
187
|
+
},
|
|
188
|
+
// Timestamp
|
|
189
|
+
showTimestamp && createElement(
|
|
190
|
+
'span',
|
|
191
|
+
{ style: { color: theme.textMuted, flexShrink: 0, minWidth: 84 } },
|
|
192
|
+
formatLogTime(entry.timestamp),
|
|
193
|
+
),
|
|
194
|
+
// Level badge
|
|
195
|
+
showLevel && createElement(
|
|
196
|
+
'span',
|
|
197
|
+
{
|
|
198
|
+
style: {
|
|
199
|
+
color: levelColor,
|
|
200
|
+
flexShrink: 0,
|
|
201
|
+
fontWeight: entry.level === 'fatal' ? 700 : 500,
|
|
202
|
+
minWidth: 56,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
getLevelLabel(entry.level),
|
|
206
|
+
),
|
|
207
|
+
// Source badge
|
|
208
|
+
showSource && entry.source && createElement(
|
|
209
|
+
'span',
|
|
210
|
+
{
|
|
211
|
+
style: {
|
|
212
|
+
background: theme.surfaceAlt,
|
|
213
|
+
border: `1px solid ${theme.border}`,
|
|
214
|
+
borderRadius: 10,
|
|
215
|
+
padding: '0 5px',
|
|
216
|
+
fontSize: 10,
|
|
217
|
+
color: theme.textMuted,
|
|
218
|
+
flexShrink: 0,
|
|
219
|
+
maxWidth: 80,
|
|
220
|
+
overflow: 'hidden',
|
|
221
|
+
textOverflow: 'ellipsis',
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
sanitizeString(entry.source),
|
|
225
|
+
),
|
|
226
|
+
// Message
|
|
227
|
+
createElement(
|
|
228
|
+
'span',
|
|
229
|
+
{ style: { flex: 1, color: theme.text, overflow: 'hidden', textOverflow: 'ellipsis' } },
|
|
230
|
+
...highlightText(entry.message, searchQuery),
|
|
231
|
+
),
|
|
232
|
+
// Expand indicator
|
|
233
|
+
hasMetadata && createElement(
|
|
234
|
+
'span',
|
|
235
|
+
{ style: { color: theme.textMuted, fontSize: 10, flexShrink: 0 } },
|
|
236
|
+
isExpanded ? '▲' : '▼',
|
|
237
|
+
),
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── MetadataPanel ────────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
function MetadataPanel({ metadata, theme, style }: {
|
|
244
|
+
metadata: Record<string, string | number | boolean>;
|
|
245
|
+
theme: ReturnType<typeof useTheme>;
|
|
246
|
+
style?: CSSProperties;
|
|
247
|
+
}) {
|
|
248
|
+
const panelStyle: CSSProperties = {
|
|
249
|
+
...style,
|
|
250
|
+
background: theme.surfaceAlt,
|
|
251
|
+
borderBottom: `1px solid ${theme.border}`,
|
|
252
|
+
padding: '6px 10px 6px 56px',
|
|
253
|
+
display: 'flex',
|
|
254
|
+
flexWrap: 'wrap',
|
|
255
|
+
gap: 6,
|
|
256
|
+
boxSizing: 'border-box',
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
return createElement(
|
|
260
|
+
'div',
|
|
261
|
+
{ style: panelStyle },
|
|
262
|
+
...Object.entries(metadata).map(([k, v]) =>
|
|
263
|
+
createElement(
|
|
264
|
+
'span',
|
|
265
|
+
{
|
|
266
|
+
key: k,
|
|
267
|
+
style: {
|
|
268
|
+
fontSize: 11,
|
|
269
|
+
fontFamily: 'monospace',
|
|
270
|
+
color: theme.textMuted,
|
|
271
|
+
background: theme.surface,
|
|
272
|
+
border: `1px solid ${theme.border}`,
|
|
273
|
+
borderRadius: 4,
|
|
274
|
+
padding: '1px 6px',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
createElement('span', { style: { color: theme.info } }, `${sanitizeString(k)}: `),
|
|
278
|
+
sanitizeString(String(v)),
|
|
279
|
+
),
|
|
280
|
+
),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── TkxLiveLog ───────────────────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
export function TkxLiveLog({
|
|
287
|
+
entries,
|
|
288
|
+
maxEntries = 500,
|
|
289
|
+
height = 400,
|
|
290
|
+
autoScroll = true,
|
|
291
|
+
showLevel = true,
|
|
292
|
+
showTimestamp = true,
|
|
293
|
+
showSource = true,
|
|
294
|
+
filterLevel,
|
|
295
|
+
searchQuery = '',
|
|
296
|
+
monospace = true,
|
|
297
|
+
onEntryClick,
|
|
298
|
+
}: TkxLiveLogProps) {
|
|
299
|
+
const theme = useTheme();
|
|
300
|
+
injectLogStyles();
|
|
301
|
+
|
|
302
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
303
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
304
|
+
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
|
305
|
+
const [userScrolledUp, setUserScrolledUp] = useState(false);
|
|
306
|
+
const prevLengthRef = useRef(entries.length);
|
|
307
|
+
|
|
308
|
+
// Filter entries
|
|
309
|
+
const filteredEntries = useMemo(() => {
|
|
310
|
+
let list = entries.slice(-maxEntries);
|
|
311
|
+
if (filterLevel !== undefined) {
|
|
312
|
+
const minOrder = LEVEL_ORDER[filterLevel];
|
|
313
|
+
list = list.filter((e) => LEVEL_ORDER[e.level] >= minOrder);
|
|
314
|
+
}
|
|
315
|
+
if (searchQuery) {
|
|
316
|
+
const q = searchQuery.toLowerCase();
|
|
317
|
+
list = list.filter(
|
|
318
|
+
(e) =>
|
|
319
|
+
e.message.toLowerCase().includes(q) ||
|
|
320
|
+
(e.source ?? '').toLowerCase().includes(q),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return list;
|
|
324
|
+
}, [entries, maxEntries, filterLevel, searchQuery]);
|
|
325
|
+
|
|
326
|
+
const containerHeight = typeof height === 'number' ? height : 400;
|
|
327
|
+
|
|
328
|
+
// Each entry may take ROW_HEIGHT, plus expansion panel (auto-height ~ 40px)
|
|
329
|
+
const METADATA_HEIGHT = 40;
|
|
330
|
+
|
|
331
|
+
// Compute cumulative offsets for variable-height virtualization
|
|
332
|
+
const offsets = useMemo(() => {
|
|
333
|
+
const arr: number[] = [];
|
|
334
|
+
let acc = 0;
|
|
335
|
+
for (const e of filteredEntries) {
|
|
336
|
+
arr.push(acc);
|
|
337
|
+
acc += ROW_HEIGHT + (expandedIds.has(e.id) ? METADATA_HEIGHT : 0);
|
|
338
|
+
}
|
|
339
|
+
arr.push(acc); // sentinel
|
|
340
|
+
return arr;
|
|
341
|
+
}, [filteredEntries, expandedIds]);
|
|
342
|
+
|
|
343
|
+
const totalHeight = offsets[offsets.length - 1] ?? 0;
|
|
344
|
+
|
|
345
|
+
// Auto-scroll
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
if (!autoScroll || userScrolledUp) return;
|
|
348
|
+
if (filteredEntries.length !== prevLengthRef.current) {
|
|
349
|
+
prevLengthRef.current = filteredEntries.length;
|
|
350
|
+
if (containerRef.current) {
|
|
351
|
+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}, [filteredEntries.length, autoScroll, userScrolledUp]);
|
|
355
|
+
|
|
356
|
+
const handleScroll = useCallback(() => {
|
|
357
|
+
if (!containerRef.current) return;
|
|
358
|
+
const el = containerRef.current;
|
|
359
|
+
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < ROW_HEIGHT * 2;
|
|
360
|
+
setUserScrolledUp(!atBottom);
|
|
361
|
+
setScrollTop(el.scrollTop);
|
|
362
|
+
}, []);
|
|
363
|
+
|
|
364
|
+
const toggleExpand = useCallback((id: string) => {
|
|
365
|
+
setExpandedIds((prev) => {
|
|
366
|
+
const next = new Set(prev);
|
|
367
|
+
if (next.has(id)) next.delete(id);
|
|
368
|
+
else next.add(id);
|
|
369
|
+
return next;
|
|
370
|
+
});
|
|
371
|
+
}, []);
|
|
372
|
+
|
|
373
|
+
// Binary search to find first visible entry
|
|
374
|
+
function findStartIndex(st: number): number {
|
|
375
|
+
let lo = 0, hi = filteredEntries.length - 1;
|
|
376
|
+
while (lo < hi) {
|
|
377
|
+
const mid = (lo + hi) >> 1;
|
|
378
|
+
if ((offsets[mid + 1] ?? 0) <= st) lo = mid + 1;
|
|
379
|
+
else hi = mid;
|
|
380
|
+
}
|
|
381
|
+
return Math.max(0, lo - OVERSCAN);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function findEndIndex(st: number): number {
|
|
385
|
+
let lo = 0, hi = filteredEntries.length - 1;
|
|
386
|
+
while (lo < hi) {
|
|
387
|
+
const mid = (lo + hi + 1) >> 1;
|
|
388
|
+
if ((offsets[mid] ?? 0) >= st + containerHeight) hi = mid - 1;
|
|
389
|
+
else lo = mid;
|
|
390
|
+
}
|
|
391
|
+
return Math.min(filteredEntries.length - 1, lo + OVERSCAN);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const startIndex = filteredEntries.length > 0 ? findStartIndex(scrollTop) : 0;
|
|
395
|
+
const endIndex = filteredEntries.length > 0 ? findEndIndex(scrollTop) : -1;
|
|
396
|
+
|
|
397
|
+
const containerStyle: CSSProperties = {
|
|
398
|
+
position: 'relative',
|
|
399
|
+
overflow: 'auto',
|
|
400
|
+
height: typeof height === 'number' ? `${height}px` : height,
|
|
401
|
+
background: theme.bg,
|
|
402
|
+
border: `1px solid ${theme.border}`,
|
|
403
|
+
borderRadius: 8,
|
|
404
|
+
boxSizing: 'border-box',
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const innerStyle: CSSProperties = {
|
|
408
|
+
position: 'relative',
|
|
409
|
+
height: totalHeight,
|
|
410
|
+
minHeight: '100%',
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
if (filteredEntries.length === 0) {
|
|
414
|
+
return createElement(
|
|
415
|
+
'div',
|
|
416
|
+
{ style: { ...containerStyle, display: 'flex', alignItems: 'center', justifyContent: 'center' } },
|
|
417
|
+
createElement('span', { style: { color: theme.textMuted, fontSize: 13, fontFamily: 'monospace' } }, 'No log entries.'),
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const rows: React.ReactNode[] = [];
|
|
422
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
423
|
+
const entry = filteredEntries[i];
|
|
424
|
+
if (!entry) continue;
|
|
425
|
+
const top = offsets[i] ?? 0;
|
|
426
|
+
const isExpanded = expandedIds.has(entry.id);
|
|
427
|
+
rows.push(
|
|
428
|
+
createElement(LogRow, {
|
|
429
|
+
key: entry.id,
|
|
430
|
+
entry,
|
|
431
|
+
showLevel,
|
|
432
|
+
showTimestamp,
|
|
433
|
+
showSource,
|
|
434
|
+
searchQuery,
|
|
435
|
+
monospace,
|
|
436
|
+
isExpanded,
|
|
437
|
+
onToggle: () => toggleExpand(entry.id),
|
|
438
|
+
onEntryClick,
|
|
439
|
+
theme,
|
|
440
|
+
style: { position: 'absolute', top, left: 0, right: 0 },
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
443
|
+
if (isExpanded && entry.metadata && Object.keys(entry.metadata).length > 0) {
|
|
444
|
+
rows.push(
|
|
445
|
+
createElement(MetadataPanel, {
|
|
446
|
+
key: `${entry.id}-meta`,
|
|
447
|
+
metadata: entry.metadata,
|
|
448
|
+
theme,
|
|
449
|
+
style: { position: 'absolute', top: top + ROW_HEIGHT, left: 0, right: 0, height: METADATA_HEIGHT },
|
|
450
|
+
}),
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return createElement(
|
|
456
|
+
'div',
|
|
457
|
+
{
|
|
458
|
+
ref: containerRef,
|
|
459
|
+
style: containerStyle,
|
|
460
|
+
onScroll: handleScroll,
|
|
461
|
+
role: 'log',
|
|
462
|
+
'aria-live': 'polite',
|
|
463
|
+
'aria-label': 'Log viewer',
|
|
464
|
+
},
|
|
465
|
+
createElement('div', { style: innerStyle }, ...rows),
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Satisfy import requirement
|
|
470
|
+
void tkx;
|