@alcyone-labs/arg-parser 2.13.1 → 2.13.3
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 +45 -2728
- package/dist/index.cjs +7 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.min.mjs +425 -424
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +6 -5
- package/dist/index.mjs.map +1 -1
- package/dist/tui/index.d.ts +292 -0
- package/dist/tui.cjs +1137 -0
- package/dist/tui.cjs.map +1 -0
- package/dist/tui.mjs +1111 -0
- package/dist/tui.mjs.map +1 -0
- package/package.json +15 -3
package/dist/tui.cjs
ADDED
|
@@ -0,0 +1,1137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const solid = require("@opentui/solid");
|
|
4
|
+
const solidJs = require("solid-js");
|
|
5
|
+
const EXIT_GUARD_STATE_KEY = /* @__PURE__ */ Symbol.for(
|
|
6
|
+
"@alcyone-labs/arg-parser/tui/ExitGuardState"
|
|
7
|
+
);
|
|
8
|
+
function getExitGuardState() {
|
|
9
|
+
return process[EXIT_GUARD_STATE_KEY];
|
|
10
|
+
}
|
|
11
|
+
function setExitGuardState(state) {
|
|
12
|
+
if (state) {
|
|
13
|
+
process[EXIT_GUARD_STATE_KEY] = state;
|
|
14
|
+
} else {
|
|
15
|
+
delete process[EXIT_GUARD_STATE_KEY];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function ExitGuard(props) {
|
|
19
|
+
const renderer = solid.useRenderer();
|
|
20
|
+
let state = getExitGuardState();
|
|
21
|
+
if (!state) {
|
|
22
|
+
const originalExit = process.exit.bind(process);
|
|
23
|
+
state = {
|
|
24
|
+
originalExit,
|
|
25
|
+
renderers: /* @__PURE__ */ new Set(),
|
|
26
|
+
activeCount: 0,
|
|
27
|
+
isExiting: false
|
|
28
|
+
};
|
|
29
|
+
setExitGuardState(state);
|
|
30
|
+
const guardedExit = ((code) => {
|
|
31
|
+
const currentState = getExitGuardState();
|
|
32
|
+
if (!currentState) {
|
|
33
|
+
return originalExit(code);
|
|
34
|
+
}
|
|
35
|
+
if (currentState.isExiting) {
|
|
36
|
+
return currentState.originalExit(code);
|
|
37
|
+
}
|
|
38
|
+
currentState.isExiting = true;
|
|
39
|
+
try {
|
|
40
|
+
if (typeof code === "number") {
|
|
41
|
+
process.exitCode = code;
|
|
42
|
+
}
|
|
43
|
+
for (const r of currentState.renderers) {
|
|
44
|
+
try {
|
|
45
|
+
r.destroy();
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} finally {
|
|
50
|
+
return currentState.originalExit(code);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
process.exit = guardedExit;
|
|
54
|
+
}
|
|
55
|
+
state.activeCount++;
|
|
56
|
+
state.renderers.add(renderer);
|
|
57
|
+
solidJs.onCleanup(() => {
|
|
58
|
+
const state2 = getExitGuardState();
|
|
59
|
+
if (!state2) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
state2.renderers.delete(renderer);
|
|
63
|
+
state2.activeCount = Math.max(0, state2.activeCount - 1);
|
|
64
|
+
if (state2.activeCount === 0) {
|
|
65
|
+
process.exit = state2.originalExit;
|
|
66
|
+
setExitGuardState(void 0);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return props.children;
|
|
70
|
+
}
|
|
71
|
+
const ShortcutContext = solidJs.createContext();
|
|
72
|
+
function ShortcutProvider(props) {
|
|
73
|
+
const [registeredBindings, setRegisteredBindings] = solidJs.createSignal(props.bindings ?? []);
|
|
74
|
+
const [pending, setPending] = solidJs.createSignal(null);
|
|
75
|
+
const register = (binding) => {
|
|
76
|
+
setRegisteredBindings((prev) => [...prev, binding]);
|
|
77
|
+
return () => {
|
|
78
|
+
setRegisteredBindings((prev) => prev.filter((b) => b !== binding));
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
const value = {
|
|
82
|
+
register,
|
|
83
|
+
pending,
|
|
84
|
+
bindings: registeredBindings
|
|
85
|
+
};
|
|
86
|
+
return solid.createComponent(ShortcutContext.Provider, {
|
|
87
|
+
value,
|
|
88
|
+
get children() {
|
|
89
|
+
return props.children;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function useShortcuts() {
|
|
94
|
+
const context = solidJs.useContext(ShortcutContext);
|
|
95
|
+
if (!context) {
|
|
96
|
+
throw new Error("useShortcuts must be used within a ShortcutProvider");
|
|
97
|
+
}
|
|
98
|
+
return context;
|
|
99
|
+
}
|
|
100
|
+
const TuiThemes = {
|
|
101
|
+
dark: {
|
|
102
|
+
name: "dark",
|
|
103
|
+
colors: {
|
|
104
|
+
text: "#ffffff",
|
|
105
|
+
muted: "#888888",
|
|
106
|
+
background: "#1a1a1a",
|
|
107
|
+
accent: "#00d4ff",
|
|
108
|
+
success: "#00ff88",
|
|
109
|
+
warning: "#ffaa00",
|
|
110
|
+
error: "#ff4444",
|
|
111
|
+
border: "#444444",
|
|
112
|
+
selection: "#0066cc"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
light: {
|
|
116
|
+
name: "light",
|
|
117
|
+
colors: {
|
|
118
|
+
text: "#000000",
|
|
119
|
+
// Black text for readability
|
|
120
|
+
muted: "#333333",
|
|
121
|
+
// Dark gray muted
|
|
122
|
+
background: "#e8e8e8",
|
|
123
|
+
// Light gray background
|
|
124
|
+
accent: "#0044aa",
|
|
125
|
+
// Deep blue accent
|
|
126
|
+
success: "#005500",
|
|
127
|
+
// Dark green
|
|
128
|
+
warning: "#885500",
|
|
129
|
+
// Dark orange
|
|
130
|
+
error: "#880000",
|
|
131
|
+
// Dark red
|
|
132
|
+
border: "#888888",
|
|
133
|
+
selection: "#0044aa"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
monokai: {
|
|
137
|
+
name: "monokai",
|
|
138
|
+
colors: {
|
|
139
|
+
text: "#f8f8f2",
|
|
140
|
+
muted: "#75715e",
|
|
141
|
+
background: "#272822",
|
|
142
|
+
accent: "#ae81ff",
|
|
143
|
+
success: "#a6e22e",
|
|
144
|
+
warning: "#e6db74",
|
|
145
|
+
error: "#f92672",
|
|
146
|
+
border: "#49483e",
|
|
147
|
+
selection: "#49483e"
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
dracula: {
|
|
151
|
+
name: "dracula",
|
|
152
|
+
colors: {
|
|
153
|
+
text: "#f8f8f2",
|
|
154
|
+
muted: "#6272a4",
|
|
155
|
+
background: "#282a36",
|
|
156
|
+
accent: "#bd93f9",
|
|
157
|
+
success: "#50fa7b",
|
|
158
|
+
warning: "#f1fa8c",
|
|
159
|
+
error: "#ff5555",
|
|
160
|
+
border: "#44475a",
|
|
161
|
+
selection: "#44475a"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
nord: {
|
|
165
|
+
name: "nord",
|
|
166
|
+
colors: {
|
|
167
|
+
text: "#eceff4",
|
|
168
|
+
muted: "#4c566a",
|
|
169
|
+
background: "#2e3440",
|
|
170
|
+
accent: "#88c0d0",
|
|
171
|
+
success: "#a3be8c",
|
|
172
|
+
warning: "#ebcb8b",
|
|
173
|
+
error: "#bf616a",
|
|
174
|
+
border: "#3b4252",
|
|
175
|
+
selection: "#4c566a"
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
solarized: {
|
|
179
|
+
name: "solarized",
|
|
180
|
+
colors: {
|
|
181
|
+
text: "#839496",
|
|
182
|
+
muted: "#586e75",
|
|
183
|
+
background: "#002b36",
|
|
184
|
+
accent: "#268bd2",
|
|
185
|
+
success: "#859900",
|
|
186
|
+
warning: "#b58900",
|
|
187
|
+
error: "#dc322f",
|
|
188
|
+
border: "#073642",
|
|
189
|
+
selection: "#073642"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const THEMES = TuiThemes;
|
|
194
|
+
const Theme = {
|
|
195
|
+
/**
|
|
196
|
+
* Start building a theme from an existing base theme.
|
|
197
|
+
*/
|
|
198
|
+
from: (base) => ({
|
|
199
|
+
/**
|
|
200
|
+
* Extend the base theme with overrides.
|
|
201
|
+
* Color overrides are shallow-merged with the base colors.
|
|
202
|
+
*/
|
|
203
|
+
extend: (overrides) => ({
|
|
204
|
+
name: overrides.name ?? `${base.name}-extended`,
|
|
205
|
+
colors: { ...base.colors, ...overrides.colors }
|
|
206
|
+
})
|
|
207
|
+
}),
|
|
208
|
+
/**
|
|
209
|
+
* Create a new theme from scratch.
|
|
210
|
+
*/
|
|
211
|
+
create: (theme) => theme,
|
|
212
|
+
/**
|
|
213
|
+
* Get all available theme names.
|
|
214
|
+
*/
|
|
215
|
+
names: () => Object.keys(TuiThemes),
|
|
216
|
+
/**
|
|
217
|
+
* Get a theme by name, with fallback to dark theme.
|
|
218
|
+
*/
|
|
219
|
+
get: (name) => TuiThemes[name] ?? TuiThemes["dark"]
|
|
220
|
+
};
|
|
221
|
+
const ThemeContext = solidJs.createContext();
|
|
222
|
+
function ThemeProvider(props) {
|
|
223
|
+
const themes = { ...TuiThemes };
|
|
224
|
+
const initialTheme = themes[props.initial ?? "dark"] ?? themes["dark"];
|
|
225
|
+
const [current, setCurrent] = solidJs.createSignal(initialTheme);
|
|
226
|
+
const setTheme = (name) => {
|
|
227
|
+
if (themes[name]) {
|
|
228
|
+
setCurrent(themes[name]);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const cycle = () => {
|
|
232
|
+
const currentName = current().name;
|
|
233
|
+
const names2 = Object.keys(themes);
|
|
234
|
+
const currentIndex = names2.indexOf(currentName);
|
|
235
|
+
const nextIndex = (currentIndex + 1) % names2.length;
|
|
236
|
+
setCurrent(themes[names2[nextIndex]]);
|
|
237
|
+
};
|
|
238
|
+
const register = (theme) => {
|
|
239
|
+
themes[theme.name] = theme;
|
|
240
|
+
};
|
|
241
|
+
const names = () => Object.keys(themes);
|
|
242
|
+
const value = {
|
|
243
|
+
current,
|
|
244
|
+
setTheme,
|
|
245
|
+
cycle,
|
|
246
|
+
register,
|
|
247
|
+
names
|
|
248
|
+
};
|
|
249
|
+
return solid.createComponent(ThemeContext.Provider, {
|
|
250
|
+
value,
|
|
251
|
+
get children() {
|
|
252
|
+
return props.children;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function useTheme() {
|
|
257
|
+
const context = solidJs.useContext(ThemeContext);
|
|
258
|
+
if (!context) {
|
|
259
|
+
throw new Error("useTheme must be used within a ThemeProvider");
|
|
260
|
+
}
|
|
261
|
+
return context;
|
|
262
|
+
}
|
|
263
|
+
const ToastContext = solidJs.createContext();
|
|
264
|
+
const DEFAULT_DURATION = 3e3;
|
|
265
|
+
function ToastProvider(props) {
|
|
266
|
+
const [state, setState] = solidJs.createSignal({
|
|
267
|
+
message: "",
|
|
268
|
+
type: "info",
|
|
269
|
+
visible: false
|
|
270
|
+
});
|
|
271
|
+
let hideTimeout = null;
|
|
272
|
+
const show = (message, type, duration) => {
|
|
273
|
+
if (hideTimeout) {
|
|
274
|
+
clearTimeout(hideTimeout);
|
|
275
|
+
}
|
|
276
|
+
setState({ message, type, visible: true });
|
|
277
|
+
hideTimeout = setTimeout(() => {
|
|
278
|
+
setState((prev) => ({ ...prev, visible: false }));
|
|
279
|
+
}, duration);
|
|
280
|
+
};
|
|
281
|
+
const hide = () => {
|
|
282
|
+
if (hideTimeout) {
|
|
283
|
+
clearTimeout(hideTimeout);
|
|
284
|
+
hideTimeout = null;
|
|
285
|
+
}
|
|
286
|
+
setState((prev) => ({ ...prev, visible: false }));
|
|
287
|
+
};
|
|
288
|
+
const value = {
|
|
289
|
+
info: (message, duration = DEFAULT_DURATION) => show(message, "info", duration),
|
|
290
|
+
success: (message, duration = DEFAULT_DURATION) => show(message, "success", duration),
|
|
291
|
+
error: (message, duration = DEFAULT_DURATION) => show(message, "error", duration),
|
|
292
|
+
warning: (message, duration = DEFAULT_DURATION) => show(message, "warning", duration),
|
|
293
|
+
state,
|
|
294
|
+
hide
|
|
295
|
+
};
|
|
296
|
+
return solid.createComponent(ToastContext.Provider, {
|
|
297
|
+
value,
|
|
298
|
+
get children() {
|
|
299
|
+
return props.children;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
function useToast() {
|
|
304
|
+
const context = solidJs.useContext(ToastContext);
|
|
305
|
+
if (!context) {
|
|
306
|
+
throw new Error("useToast must be used within a ToastProvider");
|
|
307
|
+
}
|
|
308
|
+
return context;
|
|
309
|
+
}
|
|
310
|
+
function createTuiApp(App, config = {}) {
|
|
311
|
+
const { theme = "dark", shortcuts = [], onDestroy } = config;
|
|
312
|
+
return solid.render(
|
|
313
|
+
() => solid.createComponent(ExitGuard, {
|
|
314
|
+
get children() {
|
|
315
|
+
return solid.createComponent(ThemeProvider, {
|
|
316
|
+
get initial() {
|
|
317
|
+
return theme;
|
|
318
|
+
},
|
|
319
|
+
get children() {
|
|
320
|
+
return solid.createComponent(ShortcutProvider, {
|
|
321
|
+
get bindings() {
|
|
322
|
+
return shortcuts;
|
|
323
|
+
},
|
|
324
|
+
get children() {
|
|
325
|
+
return solid.createComponent(ToastProvider, {
|
|
326
|
+
get children() {
|
|
327
|
+
return solid.createComponent(App, {});
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}),
|
|
336
|
+
{ onDestroy }
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const TuiContext = solidJs.createContext();
|
|
340
|
+
function useTui() {
|
|
341
|
+
const context = solidJs.useContext(TuiContext);
|
|
342
|
+
if (!context) {
|
|
343
|
+
throw new Error("useTui must be used within a TuiProvider");
|
|
344
|
+
}
|
|
345
|
+
return context;
|
|
346
|
+
}
|
|
347
|
+
function TuiProvider(props) {
|
|
348
|
+
const renderer = solid.useRenderer();
|
|
349
|
+
const reservedRows = props.reservedRows ?? 8;
|
|
350
|
+
const scrollSpeed = props.scrollSpeed ?? 3;
|
|
351
|
+
const [viewportHeight, setViewportHeight] = solidJs.createSignal(
|
|
352
|
+
Math.max(10, renderer.height - reservedRows)
|
|
353
|
+
);
|
|
354
|
+
const [viewportWidth, setViewportWidth] = solidJs.createSignal(renderer.width);
|
|
355
|
+
const exit = (code = 0) => {
|
|
356
|
+
process.exitCode = code;
|
|
357
|
+
renderer.destroy();
|
|
358
|
+
};
|
|
359
|
+
const handleResize = (width, height) => {
|
|
360
|
+
setViewportHeight(Math.max(10, height - reservedRows));
|
|
361
|
+
setViewportWidth(width);
|
|
362
|
+
};
|
|
363
|
+
const handleMouseScroll = (event) => {
|
|
364
|
+
if (!props.onScroll || !event.scroll) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const sign = event.scroll.direction === "up" ? -1 : event.scroll.direction === "down" ? 1 : 0;
|
|
368
|
+
if (sign === 0) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const delta = event.scroll.delta || 1;
|
|
372
|
+
props.onScroll(sign * delta * scrollSpeed);
|
|
373
|
+
};
|
|
374
|
+
solidJs.onMount(() => {
|
|
375
|
+
renderer.on("resize", handleResize);
|
|
376
|
+
});
|
|
377
|
+
solidJs.onCleanup(() => {
|
|
378
|
+
renderer.off("resize", handleResize);
|
|
379
|
+
});
|
|
380
|
+
const contextValue = {
|
|
381
|
+
viewportHeight,
|
|
382
|
+
viewportWidth,
|
|
383
|
+
exit
|
|
384
|
+
};
|
|
385
|
+
const themeName = typeof props.theme === "string" ? props.theme : props.theme?.name ?? "dark";
|
|
386
|
+
return solid.createComponent(ExitGuard, {
|
|
387
|
+
get children() {
|
|
388
|
+
return solid.createComponent(TuiContext.Provider, {
|
|
389
|
+
value: contextValue,
|
|
390
|
+
get children() {
|
|
391
|
+
return solid.createComponent(ThemeProvider, {
|
|
392
|
+
initial: themeName,
|
|
393
|
+
get children() {
|
|
394
|
+
return solid.createComponent(ShortcutProvider, {
|
|
395
|
+
get bindings() {
|
|
396
|
+
return props.shortcuts ?? [];
|
|
397
|
+
},
|
|
398
|
+
get children() {
|
|
399
|
+
return solid.createComponent(ToastProvider, {
|
|
400
|
+
get children() {
|
|
401
|
+
return /* @__PURE__ */ React.createElement(
|
|
402
|
+
"box",
|
|
403
|
+
{
|
|
404
|
+
width: "100%",
|
|
405
|
+
height: "100%",
|
|
406
|
+
onMouseScroll: handleMouseScroll
|
|
407
|
+
},
|
|
408
|
+
props.children
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
function Breadcrumb(props) {
|
|
422
|
+
const { current: theme } = useTheme();
|
|
423
|
+
const separator = props.separator ?? "›";
|
|
424
|
+
const accentColor = () => props.accentColor ?? theme().colors.accent;
|
|
425
|
+
const mutedColor = () => props.mutedColor ?? theme().colors.muted;
|
|
426
|
+
return /* @__PURE__ */ React.createElement("box", { height: 1, paddingLeft: 2 }, /* @__PURE__ */ React.createElement(solidJs.For, { each: props.segments }, (segment, idx) => /* @__PURE__ */ React.createElement(React.Fragment, null, idx() > 0 && /* @__PURE__ */ React.createElement("text", { color: mutedColor() }, " ", separator, " "), /* @__PURE__ */ React.createElement("text", { color: accentColor(), bold: true }, segment))));
|
|
427
|
+
}
|
|
428
|
+
function unwrap(value) {
|
|
429
|
+
return typeof value === "function" ? value() : value;
|
|
430
|
+
}
|
|
431
|
+
function VirtualList(props) {
|
|
432
|
+
const { current: theme } = useTheme();
|
|
433
|
+
const [scrollOffset, setScrollOffset] = solidJs.createSignal(0);
|
|
434
|
+
const items = () => unwrap(props.items);
|
|
435
|
+
const selectedIndex = () => unwrap(props.selectedIndex);
|
|
436
|
+
const viewportHeight = () => unwrap(props.viewportHeight ?? 20);
|
|
437
|
+
const showIndicator = props.showIndicator ?? true;
|
|
438
|
+
const visibleItems = solidJs.createMemo(() => {
|
|
439
|
+
const allItems = items();
|
|
440
|
+
const vh = viewportHeight();
|
|
441
|
+
const start = scrollOffset();
|
|
442
|
+
const end = Math.min(start + vh, allItems.length);
|
|
443
|
+
return allItems.slice(start, end).map((item, localIdx) => ({
|
|
444
|
+
item,
|
|
445
|
+
globalIndex: start + localIdx
|
|
446
|
+
}));
|
|
447
|
+
});
|
|
448
|
+
const defaultRenderItem = (item, index, selected) => {
|
|
449
|
+
const label = props.getLabel ? props.getLabel(item) : String(item);
|
|
450
|
+
const t = theme();
|
|
451
|
+
return /* @__PURE__ */ React.createElement(
|
|
452
|
+
"box",
|
|
453
|
+
{
|
|
454
|
+
height: 1,
|
|
455
|
+
backgroundColor: selected ? t.colors.selection : void 0
|
|
456
|
+
},
|
|
457
|
+
/* @__PURE__ */ React.createElement("text", { color: selected ? t.colors.background : t.colors.text }, showIndicator ? selected ? "› " : " " : "", label)
|
|
458
|
+
);
|
|
459
|
+
};
|
|
460
|
+
const renderItem = props.renderItem ?? defaultRenderItem;
|
|
461
|
+
return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement(solidJs.Show, { when: props.title }, /* @__PURE__ */ React.createElement("text", { bold: true, color: theme().colors.text, marginBottom: 1 }, props.title, " (", selectedIndex() + 1, "/", items().length, ")")), /* @__PURE__ */ React.createElement(solidJs.For, { each: visibleItems() }, ({ item, globalIndex }) => renderItem(item, globalIndex, globalIndex === selectedIndex())));
|
|
462
|
+
}
|
|
463
|
+
function createVirtualListController(items, selectedIndex, setSelectedIndex, viewportHeight) {
|
|
464
|
+
const [scrollOffset, setScrollOffset] = solidJs.createSignal(0);
|
|
465
|
+
const adjustScroll = (newIdx) => {
|
|
466
|
+
const vh = viewportHeight();
|
|
467
|
+
const currentOffset = scrollOffset();
|
|
468
|
+
if (newIdx < currentOffset) {
|
|
469
|
+
setScrollOffset(newIdx);
|
|
470
|
+
} else if (newIdx >= currentOffset + vh) {
|
|
471
|
+
setScrollOffset(newIdx - vh + 1);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
const scrollBy = (delta) => {
|
|
475
|
+
const maxOffset = Math.max(0, items().length - viewportHeight());
|
|
476
|
+
setScrollOffset((o) => Math.max(0, Math.min(maxOffset, o + delta)));
|
|
477
|
+
};
|
|
478
|
+
const selectPrevious = () => {
|
|
479
|
+
const newIdx = Math.max(0, selectedIndex() - 1);
|
|
480
|
+
setSelectedIndex(newIdx);
|
|
481
|
+
adjustScroll(newIdx);
|
|
482
|
+
};
|
|
483
|
+
const selectNext = () => {
|
|
484
|
+
const newIdx = Math.min(items().length - 1, selectedIndex() + 1);
|
|
485
|
+
setSelectedIndex(newIdx);
|
|
486
|
+
adjustScroll(newIdx);
|
|
487
|
+
};
|
|
488
|
+
return { scrollOffset, adjustScroll, scrollBy, selectPrevious, selectNext };
|
|
489
|
+
}
|
|
490
|
+
function MasterDetail(props) {
|
|
491
|
+
const { current: theme } = useTheme();
|
|
492
|
+
const masterWidth = props.masterWidth ?? "35%";
|
|
493
|
+
return /* @__PURE__ */ React.createElement(
|
|
494
|
+
"box",
|
|
495
|
+
{
|
|
496
|
+
width: "100%",
|
|
497
|
+
height: "100%",
|
|
498
|
+
flexDirection: "column",
|
|
499
|
+
backgroundColor: theme().colors.background
|
|
500
|
+
},
|
|
501
|
+
/* @__PURE__ */ React.createElement(
|
|
502
|
+
"box",
|
|
503
|
+
{
|
|
504
|
+
height: 3,
|
|
505
|
+
borderStyle: "single",
|
|
506
|
+
borderColor: theme().colors.accent,
|
|
507
|
+
justifyContent: "center",
|
|
508
|
+
alignItems: "center"
|
|
509
|
+
},
|
|
510
|
+
/* @__PURE__ */ React.createElement("text", { bold: true, color: theme().colors.accent }, props.headerIcon ? ` ${props.headerIcon} ` : " ", props.header, " ")
|
|
511
|
+
),
|
|
512
|
+
/* @__PURE__ */ React.createElement(solidJs.Show, { when: props.breadcrumb && props.breadcrumb.length > 0 }, /* @__PURE__ */ React.createElement(Breadcrumb, { segments: props.breadcrumb })),
|
|
513
|
+
/* @__PURE__ */ React.createElement("box", { flexGrow: 1, flexDirection: "row" }, /* @__PURE__ */ React.createElement(
|
|
514
|
+
"box",
|
|
515
|
+
{
|
|
516
|
+
width: masterWidth,
|
|
517
|
+
borderStyle: "single",
|
|
518
|
+
borderColor: theme().colors.border,
|
|
519
|
+
flexDirection: "column",
|
|
520
|
+
padding: 1
|
|
521
|
+
},
|
|
522
|
+
props.master
|
|
523
|
+
), /* @__PURE__ */ React.createElement(
|
|
524
|
+
"box",
|
|
525
|
+
{
|
|
526
|
+
flexGrow: 1,
|
|
527
|
+
borderStyle: "single",
|
|
528
|
+
borderColor: theme().colors.border,
|
|
529
|
+
flexDirection: "column",
|
|
530
|
+
padding: 2
|
|
531
|
+
},
|
|
532
|
+
props.detail
|
|
533
|
+
)),
|
|
534
|
+
/* @__PURE__ */ React.createElement(solidJs.Show, { when: props.footer }, /* @__PURE__ */ React.createElement("box", { height: 1, backgroundColor: theme().colors.background }, /* @__PURE__ */ React.createElement("text", { color: theme().colors.muted }, " ", props.footer)))
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
function parseWidth(width) {
|
|
538
|
+
if (width === void 0) return "30%";
|
|
539
|
+
if (typeof width === "number") return width;
|
|
540
|
+
if (width.endsWith("%")) {
|
|
541
|
+
const percent = parseInt(width.replace("%", ""), 10);
|
|
542
|
+
return `${percent}%`;
|
|
543
|
+
}
|
|
544
|
+
return width;
|
|
545
|
+
}
|
|
546
|
+
function MasterDetailLayout$1(props) {
|
|
547
|
+
const masterWidth = parseWidth(props.masterWidth);
|
|
548
|
+
const gap = props.gap ?? 1;
|
|
549
|
+
const showDivider = props.showDivider ?? true;
|
|
550
|
+
return {
|
|
551
|
+
type: "box",
|
|
552
|
+
props: {
|
|
553
|
+
flexDirection: "row",
|
|
554
|
+
width: "100%",
|
|
555
|
+
height: "100%"
|
|
556
|
+
},
|
|
557
|
+
children: [
|
|
558
|
+
// Master panel
|
|
559
|
+
{
|
|
560
|
+
type: "box",
|
|
561
|
+
props: {
|
|
562
|
+
flexBasis: masterWidth,
|
|
563
|
+
flexShrink: 0,
|
|
564
|
+
overflow: "scroll"
|
|
565
|
+
},
|
|
566
|
+
children: props.master
|
|
567
|
+
},
|
|
568
|
+
// Divider (optional)
|
|
569
|
+
...showDivider ? [
|
|
570
|
+
{
|
|
571
|
+
type: "box",
|
|
572
|
+
props: {
|
|
573
|
+
width: 1,
|
|
574
|
+
marginLeft: Math.floor(gap / 2),
|
|
575
|
+
marginRight: Math.ceil(gap / 2)
|
|
576
|
+
},
|
|
577
|
+
children: {
|
|
578
|
+
type: "text",
|
|
579
|
+
props: {
|
|
580
|
+
style: { fg: "#444444" }
|
|
581
|
+
},
|
|
582
|
+
children: "│".repeat(100)
|
|
583
|
+
// Repeating for height
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
] : [],
|
|
587
|
+
// Detail panel
|
|
588
|
+
{
|
|
589
|
+
type: "box",
|
|
590
|
+
props: {
|
|
591
|
+
flexGrow: 1,
|
|
592
|
+
overflow: "scroll"
|
|
593
|
+
},
|
|
594
|
+
children: props.detail
|
|
595
|
+
}
|
|
596
|
+
]
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function DrillDownNavigator(props) {
|
|
600
|
+
const [stack, setStack] = solidJs.createSignal([]);
|
|
601
|
+
const push = (view) => {
|
|
602
|
+
setStack((prev) => [...prev, view]);
|
|
603
|
+
props.onNavigate?.(stack().length + 1);
|
|
604
|
+
};
|
|
605
|
+
const pop = () => {
|
|
606
|
+
if (stack().length > 0) {
|
|
607
|
+
setStack((prev) => prev.slice(0, -1));
|
|
608
|
+
props.onNavigate?.(stack().length - 1);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
const replace = (view) => {
|
|
612
|
+
if (stack().length > 0) {
|
|
613
|
+
setStack((prev) => [...prev.slice(0, -1), view]);
|
|
614
|
+
} else {
|
|
615
|
+
push(view);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
const reset = () => {
|
|
619
|
+
setStack([]);
|
|
620
|
+
props.onNavigate?.(0);
|
|
621
|
+
};
|
|
622
|
+
const depth = () => stack().length;
|
|
623
|
+
const canGoBack = () => stack().length > 0;
|
|
624
|
+
const api = {
|
|
625
|
+
push,
|
|
626
|
+
pop,
|
|
627
|
+
replace,
|
|
628
|
+
reset,
|
|
629
|
+
depth,
|
|
630
|
+
canGoBack
|
|
631
|
+
};
|
|
632
|
+
const currentView = () => {
|
|
633
|
+
const currentStack = stack();
|
|
634
|
+
if (currentStack.length > 0) {
|
|
635
|
+
return currentStack[currentStack.length - 1]();
|
|
636
|
+
}
|
|
637
|
+
return props.children(api);
|
|
638
|
+
};
|
|
639
|
+
return {
|
|
640
|
+
type: "box",
|
|
641
|
+
props: {
|
|
642
|
+
width: "100%",
|
|
643
|
+
height: "100%",
|
|
644
|
+
onKeyDown: (event) => {
|
|
645
|
+
if (event.defaultPrevented) return;
|
|
646
|
+
if ((event.key === "Escape" || event.key === "ArrowLeft") && canGoBack()) {
|
|
647
|
+
pop();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
children: currentView()
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function Card(props) {
|
|
655
|
+
const borderStyle = props.borderStyle ?? "single";
|
|
656
|
+
const padding = props.padding ?? 1;
|
|
657
|
+
let borderColor = props.borderColor;
|
|
658
|
+
if (!borderColor) {
|
|
659
|
+
try {
|
|
660
|
+
const { current } = useTheme();
|
|
661
|
+
borderColor = current().colors.border;
|
|
662
|
+
} catch {
|
|
663
|
+
borderColor = "#444444";
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
type: "box",
|
|
668
|
+
props: {
|
|
669
|
+
flexDirection: "column",
|
|
670
|
+
width: props.width,
|
|
671
|
+
height: props.height,
|
|
672
|
+
...props.onClick && { onMouseDown: props.onClick },
|
|
673
|
+
style: {
|
|
674
|
+
border: borderStyle !== "none" ? borderStyle : void 0,
|
|
675
|
+
borderColor,
|
|
676
|
+
padding
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
children: [
|
|
680
|
+
// Title row (if provided)
|
|
681
|
+
...props.title ? [
|
|
682
|
+
{
|
|
683
|
+
type: "text",
|
|
684
|
+
props: {
|
|
685
|
+
style: { bold: true }
|
|
686
|
+
},
|
|
687
|
+
children: ` ${props.title} `
|
|
688
|
+
}
|
|
689
|
+
] : [],
|
|
690
|
+
// Content
|
|
691
|
+
props.children
|
|
692
|
+
]
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function formatValue(value, format, currency) {
|
|
696
|
+
if (typeof value === "string") return value;
|
|
697
|
+
switch (format) {
|
|
698
|
+
case "compact":
|
|
699
|
+
if (value >= 1e9)
|
|
700
|
+
return `${(value / 1e9).toFixed(1)}B`;
|
|
701
|
+
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
702
|
+
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
703
|
+
return value.toString();
|
|
704
|
+
case "percent":
|
|
705
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
706
|
+
case "currency":
|
|
707
|
+
return new Intl.NumberFormat("en-US", {
|
|
708
|
+
style: "currency",
|
|
709
|
+
currency: currency ?? "USD",
|
|
710
|
+
minimumFractionDigits: 0,
|
|
711
|
+
maximumFractionDigits: 2
|
|
712
|
+
}).format(value);
|
|
713
|
+
case "number":
|
|
714
|
+
default:
|
|
715
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function getTrendIndicator(trend) {
|
|
719
|
+
switch (trend) {
|
|
720
|
+
case "up":
|
|
721
|
+
return { symbol: "▲", color: "#00ff88" };
|
|
722
|
+
case "down":
|
|
723
|
+
return { symbol: "▼", color: "#ff4444" };
|
|
724
|
+
case "neutral":
|
|
725
|
+
default:
|
|
726
|
+
return { symbol: "─", color: "#888888" };
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
function StatCard(props) {
|
|
730
|
+
const formattedValue = formatValue(
|
|
731
|
+
props.value,
|
|
732
|
+
props.format ?? "number",
|
|
733
|
+
props.currency
|
|
734
|
+
);
|
|
735
|
+
const trendIndicator = props.trend ? getTrendIndicator(props.trend) : null;
|
|
736
|
+
return {
|
|
737
|
+
type: "box",
|
|
738
|
+
props: {
|
|
739
|
+
flexDirection: "column",
|
|
740
|
+
width: props.width,
|
|
741
|
+
padding: 1,
|
|
742
|
+
style: {
|
|
743
|
+
border: "single"
|
|
744
|
+
},
|
|
745
|
+
...props.onClick && { onMouseDown: props.onClick }
|
|
746
|
+
},
|
|
747
|
+
children: [
|
|
748
|
+
// Label
|
|
749
|
+
{
|
|
750
|
+
type: "text",
|
|
751
|
+
props: {
|
|
752
|
+
style: { fg: "#888888" }
|
|
753
|
+
},
|
|
754
|
+
children: props.label
|
|
755
|
+
},
|
|
756
|
+
// Value with optional trend
|
|
757
|
+
{
|
|
758
|
+
type: "box",
|
|
759
|
+
props: {
|
|
760
|
+
flexDirection: "row",
|
|
761
|
+
gap: 1
|
|
762
|
+
},
|
|
763
|
+
children: [
|
|
764
|
+
{
|
|
765
|
+
type: "text",
|
|
766
|
+
props: {
|
|
767
|
+
style: { bold: true, fg: "#ffffff" }
|
|
768
|
+
},
|
|
769
|
+
children: formattedValue
|
|
770
|
+
},
|
|
771
|
+
...trendIndicator ? [
|
|
772
|
+
{
|
|
773
|
+
type: "text",
|
|
774
|
+
props: {
|
|
775
|
+
style: { fg: trendIndicator.color }
|
|
776
|
+
},
|
|
777
|
+
children: trendIndicator.symbol
|
|
778
|
+
}
|
|
779
|
+
] : []
|
|
780
|
+
]
|
|
781
|
+
}
|
|
782
|
+
]
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
function MarkdownBlock(props) {
|
|
786
|
+
const { current } = useTheme();
|
|
787
|
+
return {
|
|
788
|
+
type: "box",
|
|
789
|
+
props: {
|
|
790
|
+
flexDirection: "column",
|
|
791
|
+
width: props.width ?? "100%",
|
|
792
|
+
height: props.height,
|
|
793
|
+
padding: props.padding ?? 0,
|
|
794
|
+
overflow: "scroll"
|
|
795
|
+
},
|
|
796
|
+
children: [
|
|
797
|
+
{
|
|
798
|
+
type: "text",
|
|
799
|
+
props: {
|
|
800
|
+
style: {
|
|
801
|
+
fg: current().colors.text
|
|
802
|
+
},
|
|
803
|
+
text: props.content
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
]
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function Button(props) {
|
|
810
|
+
const { current } = useTheme();
|
|
811
|
+
const [isHovered, setIsHovered] = solidJs.createSignal(false);
|
|
812
|
+
const [isPressed, setIsPressed] = solidJs.createSignal(false);
|
|
813
|
+
const getBackgroundColor = () => {
|
|
814
|
+
if (props.disabled) return current().colors.muted;
|
|
815
|
+
switch (props.variant) {
|
|
816
|
+
case "danger":
|
|
817
|
+
return isPressed() ? "#bd2c00" : isHovered() ? "#c82829" : current().colors.error;
|
|
818
|
+
case "primary":
|
|
819
|
+
default:
|
|
820
|
+
return isPressed() ? current().colors.selection : isHovered() ? current().colors.accent : current().colors.accent;
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
const getTextColor = () => {
|
|
824
|
+
if (props.disabled) return current().colors.background;
|
|
825
|
+
return props.variant === "primary" || props.variant === "danger" ? "#ffffff" : current().colors.text;
|
|
826
|
+
};
|
|
827
|
+
return {
|
|
828
|
+
type: "box",
|
|
829
|
+
props: {
|
|
830
|
+
width: props.width,
|
|
831
|
+
height: 3,
|
|
832
|
+
// Standard button height with border
|
|
833
|
+
flexDirection: "row",
|
|
834
|
+
alignItems: "center",
|
|
835
|
+
justifyContent: "center",
|
|
836
|
+
style: {
|
|
837
|
+
border: "single",
|
|
838
|
+
borderColor: isHovered() && !props.disabled ? current().colors.text : current().colors.border,
|
|
839
|
+
bg: getBackgroundColor()
|
|
840
|
+
},
|
|
841
|
+
// Event handlers
|
|
842
|
+
onMouseOver: () => !props.disabled && setIsHovered(true),
|
|
843
|
+
onMouseOut: () => {
|
|
844
|
+
setIsHovered(false);
|
|
845
|
+
setIsPressed(false);
|
|
846
|
+
},
|
|
847
|
+
onMouseDown: () => !props.disabled && setIsPressed(true),
|
|
848
|
+
onMouseUp: () => {
|
|
849
|
+
if (!props.disabled && isPressed()) {
|
|
850
|
+
setIsPressed(false);
|
|
851
|
+
props.onClick?.();
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
children: [
|
|
856
|
+
{
|
|
857
|
+
type: "text",
|
|
858
|
+
props: {
|
|
859
|
+
style: {
|
|
860
|
+
fg: getTextColor(),
|
|
861
|
+
bold: true
|
|
862
|
+
},
|
|
863
|
+
text: ` ${props.label} `
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
]
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
const LAYOUT_THEMES = {
|
|
870
|
+
dark: {
|
|
871
|
+
bg: "#0d0d0d",
|
|
872
|
+
fg: "#f5f5f5",
|
|
873
|
+
accent: "#00d4ff",
|
|
874
|
+
muted: "#999999",
|
|
875
|
+
error: "#ff4444",
|
|
876
|
+
success: "#44ff44",
|
|
877
|
+
border: "#444444",
|
|
878
|
+
selection: "#00d4ff",
|
|
879
|
+
selectionFg: "#000000"
|
|
880
|
+
},
|
|
881
|
+
light: {
|
|
882
|
+
bg: "#e8e8e8",
|
|
883
|
+
fg: "#000000",
|
|
884
|
+
accent: "#0044aa",
|
|
885
|
+
muted: "#333333",
|
|
886
|
+
error: "#880000",
|
|
887
|
+
success: "#005500",
|
|
888
|
+
border: "#888888",
|
|
889
|
+
selection: "#0044aa",
|
|
890
|
+
selectionFg: "#ffffff"
|
|
891
|
+
},
|
|
892
|
+
monokai: {
|
|
893
|
+
bg: "#272822",
|
|
894
|
+
fg: "#f8f8f2",
|
|
895
|
+
accent: "#a6e22e",
|
|
896
|
+
muted: "#75715e",
|
|
897
|
+
error: "#f92672",
|
|
898
|
+
success: "#a6e22e",
|
|
899
|
+
border: "#49483e",
|
|
900
|
+
selection: "#a6e22e",
|
|
901
|
+
selectionFg: "#272822"
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
function resolveTheme(theme) {
|
|
905
|
+
if (!theme) return LAYOUT_THEMES.dark;
|
|
906
|
+
if (typeof theme === "string")
|
|
907
|
+
return LAYOUT_THEMES[theme] ?? LAYOUT_THEMES.dark;
|
|
908
|
+
return theme;
|
|
909
|
+
}
|
|
910
|
+
function MasterDetailLayout(props) {
|
|
911
|
+
const t = resolveTheme(props.theme);
|
|
912
|
+
props.masterWidth ?? "35%";
|
|
913
|
+
return /* @__PURE__ */ React.createElement(
|
|
914
|
+
"box",
|
|
915
|
+
{
|
|
916
|
+
width: "100%",
|
|
917
|
+
height: "100%",
|
|
918
|
+
flexDirection: "column",
|
|
919
|
+
backgroundColor: t.bg
|
|
920
|
+
},
|
|
921
|
+
/* @__PURE__ */ React.createElement(
|
|
922
|
+
"box",
|
|
923
|
+
{
|
|
924
|
+
height: 3,
|
|
925
|
+
borderStyle: "single",
|
|
926
|
+
borderColor: t.accent,
|
|
927
|
+
justifyContent: "center",
|
|
928
|
+
alignItems: "center"
|
|
929
|
+
},
|
|
930
|
+
/* @__PURE__ */ React.createElement("text", { bold: true, color: t.accent }, " ", props.header, " ")
|
|
931
|
+
),
|
|
932
|
+
props.breadcrumb && props.breadcrumb.length > 0 && /* @__PURE__ */ React.createElement("box", { height: 1, paddingLeft: 2, backgroundColor: t.bg }, props.breadcrumb.map((segment, idx) => /* @__PURE__ */ React.createElement(React.Fragment, null, idx > 0 && /* @__PURE__ */ React.createElement("text", { color: t.muted }, " › "), /* @__PURE__ */ React.createElement("text", { color: t.accent, bold: true }, segment)))),
|
|
933
|
+
/* @__PURE__ */ React.createElement("box", { flexGrow: 1, flexDirection: "row" }, props.children),
|
|
934
|
+
props.footer && /* @__PURE__ */ React.createElement("box", { height: 1, backgroundColor: t.bg }, /* @__PURE__ */ React.createElement("text", { color: t.muted }, " ", props.footer))
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
function MasterPanel(props) {
|
|
938
|
+
const t = resolveTheme(props.theme);
|
|
939
|
+
return /* @__PURE__ */ React.createElement(
|
|
940
|
+
"box",
|
|
941
|
+
{
|
|
942
|
+
width: props.width ?? "35%",
|
|
943
|
+
borderStyle: "single",
|
|
944
|
+
borderColor: t.border,
|
|
945
|
+
flexDirection: "column",
|
|
946
|
+
padding: 1
|
|
947
|
+
},
|
|
948
|
+
props.children
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
function DetailPanel(props) {
|
|
952
|
+
const t = resolveTheme(props.theme);
|
|
953
|
+
return /* @__PURE__ */ React.createElement(
|
|
954
|
+
"box",
|
|
955
|
+
{
|
|
956
|
+
flexGrow: 1,
|
|
957
|
+
borderStyle: "single",
|
|
958
|
+
borderColor: t.border,
|
|
959
|
+
flexDirection: "column",
|
|
960
|
+
padding: 2
|
|
961
|
+
},
|
|
962
|
+
props.children
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
function ListItem(props) {
|
|
966
|
+
const t = resolveTheme(props.theme);
|
|
967
|
+
return /* @__PURE__ */ React.createElement("box", { height: 1, backgroundColor: props.selected ? t.selection : void 0 }, /* @__PURE__ */ React.createElement("text", { color: props.selected ? t.selectionFg : t.fg }, props.selected ? "› " : " ", props.label));
|
|
968
|
+
}
|
|
969
|
+
function useVirtualScroll(items, _selectedIdx, viewportHeight) {
|
|
970
|
+
const [scrollOffset, setScrollOffset] = solidJs.createSignal(0);
|
|
971
|
+
const adjustScroll = (newIdx) => {
|
|
972
|
+
const vh = viewportHeight();
|
|
973
|
+
const currentOffset = scrollOffset();
|
|
974
|
+
if (newIdx < currentOffset) {
|
|
975
|
+
setScrollOffset(newIdx);
|
|
976
|
+
} else if (newIdx >= currentOffset + vh) {
|
|
977
|
+
setScrollOffset(newIdx - vh + 1);
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
const visibleItems = solidJs.createMemo(() => {
|
|
981
|
+
const allItems = items();
|
|
982
|
+
const vh = viewportHeight();
|
|
983
|
+
const start = scrollOffset();
|
|
984
|
+
const end = Math.min(start + vh, allItems.length);
|
|
985
|
+
return allItems.slice(start, end).map((item, localIdx) => ({
|
|
986
|
+
item,
|
|
987
|
+
globalIndex: start + localIdx
|
|
988
|
+
}));
|
|
989
|
+
});
|
|
990
|
+
const scrollBy = (delta) => {
|
|
991
|
+
const maxOffset = Math.max(0, items().length - viewportHeight());
|
|
992
|
+
setScrollOffset((o) => Math.max(0, Math.min(maxOffset, o + delta)));
|
|
993
|
+
};
|
|
994
|
+
return { visibleItems, adjustScroll, scrollOffset, scrollBy };
|
|
995
|
+
}
|
|
996
|
+
function getViewportHeight(reservedRows = 8, minHeight = 10) {
|
|
997
|
+
return Math.max(minHeight, (process.stdout.rows || 24) - reservedRows);
|
|
998
|
+
}
|
|
999
|
+
function enableMouseReporting() {
|
|
1000
|
+
process.stdout.write("\x1B[?1000h");
|
|
1001
|
+
process.stdout.write("\x1B[?1006h");
|
|
1002
|
+
}
|
|
1003
|
+
function disableMouseReporting() {
|
|
1004
|
+
process.stdout.write("\x1B[?1000l");
|
|
1005
|
+
process.stdout.write("\x1B[?1002l");
|
|
1006
|
+
process.stdout.write("\x1B[?1003l");
|
|
1007
|
+
process.stdout.write("\x1B[?1006l");
|
|
1008
|
+
process.stdout.write("\x1B[?1015l");
|
|
1009
|
+
}
|
|
1010
|
+
function clearScreen() {
|
|
1011
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
1012
|
+
}
|
|
1013
|
+
function resetAttributes() {
|
|
1014
|
+
process.stdout.write("\x1B[0m");
|
|
1015
|
+
}
|
|
1016
|
+
function restoreStdin() {
|
|
1017
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
1018
|
+
try {
|
|
1019
|
+
process.stdin.setRawMode(false);
|
|
1020
|
+
} catch {
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function switchToMainScreen() {
|
|
1025
|
+
process.stdout.write("\x1B[?1049l");
|
|
1026
|
+
}
|
|
1027
|
+
function cleanupTerminal() {
|
|
1028
|
+
switchToMainScreen();
|
|
1029
|
+
disableMouseReporting();
|
|
1030
|
+
clearScreen();
|
|
1031
|
+
resetAttributes();
|
|
1032
|
+
restoreStdin();
|
|
1033
|
+
}
|
|
1034
|
+
function parseMouseScroll(data) {
|
|
1035
|
+
const str = data.toString();
|
|
1036
|
+
const sgrMatch = str.match(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
1037
|
+
if (sgrMatch) {
|
|
1038
|
+
const button = parseInt(sgrMatch[1], 10);
|
|
1039
|
+
if (button === 64) return -1;
|
|
1040
|
+
if (button === 65) return 1;
|
|
1041
|
+
}
|
|
1042
|
+
return 0;
|
|
1043
|
+
}
|
|
1044
|
+
function useMouse(options = {}) {
|
|
1045
|
+
const { onScroll, scrollSpeed = 3 } = options;
|
|
1046
|
+
const handleInput = (data) => {
|
|
1047
|
+
const scrollDir = parseMouseScroll(data);
|
|
1048
|
+
if (scrollDir !== 0 && onScroll) {
|
|
1049
|
+
onScroll(scrollDir * scrollSpeed);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
solidJs.onMount(() => {
|
|
1053
|
+
enableMouseReporting();
|
|
1054
|
+
if (process.stdin.isTTY) {
|
|
1055
|
+
process.stdin.setRawMode(true);
|
|
1056
|
+
}
|
|
1057
|
+
process.stdin.on("data", handleInput);
|
|
1058
|
+
});
|
|
1059
|
+
solidJs.onCleanup(() => {
|
|
1060
|
+
disableMouseReporting();
|
|
1061
|
+
process.stdin.off("data", handleInput);
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
Object.defineProperty(exports, "createComponent", {
|
|
1065
|
+
enumerable: true,
|
|
1066
|
+
get: () => solid.createComponent
|
|
1067
|
+
});
|
|
1068
|
+
Object.defineProperty(exports, "effect", {
|
|
1069
|
+
enumerable: true,
|
|
1070
|
+
get: () => solid.effect
|
|
1071
|
+
});
|
|
1072
|
+
Object.defineProperty(exports, "insert", {
|
|
1073
|
+
enumerable: true,
|
|
1074
|
+
get: () => solid.insert
|
|
1075
|
+
});
|
|
1076
|
+
Object.defineProperty(exports, "memo", {
|
|
1077
|
+
enumerable: true,
|
|
1078
|
+
get: () => solid.memo
|
|
1079
|
+
});
|
|
1080
|
+
Object.defineProperty(exports, "mergeProps", {
|
|
1081
|
+
enumerable: true,
|
|
1082
|
+
get: () => solid.mergeProps
|
|
1083
|
+
});
|
|
1084
|
+
Object.defineProperty(exports, "render", {
|
|
1085
|
+
enumerable: true,
|
|
1086
|
+
get: () => solid.render
|
|
1087
|
+
});
|
|
1088
|
+
Object.defineProperty(exports, "spread", {
|
|
1089
|
+
enumerable: true,
|
|
1090
|
+
get: () => solid.spread
|
|
1091
|
+
});
|
|
1092
|
+
Object.defineProperty(exports, "useKeyboard", {
|
|
1093
|
+
enumerable: true,
|
|
1094
|
+
get: () => solid.useKeyboard
|
|
1095
|
+
});
|
|
1096
|
+
Object.defineProperty(exports, "useRenderer", {
|
|
1097
|
+
enumerable: true,
|
|
1098
|
+
get: () => solid.useRenderer
|
|
1099
|
+
});
|
|
1100
|
+
exports.Breadcrumb = Breadcrumb;
|
|
1101
|
+
exports.Button = Button;
|
|
1102
|
+
exports.Card = Card;
|
|
1103
|
+
exports.DetailPanel = DetailPanel;
|
|
1104
|
+
exports.DrillDownNavigator = DrillDownNavigator;
|
|
1105
|
+
exports.LAYOUT_THEMES = LAYOUT_THEMES;
|
|
1106
|
+
exports.ListItem = ListItem;
|
|
1107
|
+
exports.MarkdownBlock = MarkdownBlock;
|
|
1108
|
+
exports.MasterDetail = MasterDetail;
|
|
1109
|
+
exports.MasterDetailLayout = MasterDetailLayout$1;
|
|
1110
|
+
exports.MasterDetailTemplate = MasterDetailLayout;
|
|
1111
|
+
exports.MasterPanel = MasterPanel;
|
|
1112
|
+
exports.ShortcutProvider = ShortcutProvider;
|
|
1113
|
+
exports.StatCard = StatCard;
|
|
1114
|
+
exports.THEMES = THEMES;
|
|
1115
|
+
exports.Theme = Theme;
|
|
1116
|
+
exports.ThemeProvider = ThemeProvider;
|
|
1117
|
+
exports.ToastProvider = ToastProvider;
|
|
1118
|
+
exports.TuiProvider = TuiProvider;
|
|
1119
|
+
exports.TuiThemes = TuiThemes;
|
|
1120
|
+
exports.VirtualList = VirtualList;
|
|
1121
|
+
exports.cleanupTerminal = cleanupTerminal;
|
|
1122
|
+
exports.clearScreen = clearScreen;
|
|
1123
|
+
exports.createTuiApp = createTuiApp;
|
|
1124
|
+
exports.createVirtualListController = createVirtualListController;
|
|
1125
|
+
exports.disableMouseReporting = disableMouseReporting;
|
|
1126
|
+
exports.enableMouseReporting = enableMouseReporting;
|
|
1127
|
+
exports.getViewportHeight = getViewportHeight;
|
|
1128
|
+
exports.parseMouseScroll = parseMouseScroll;
|
|
1129
|
+
exports.resetAttributes = resetAttributes;
|
|
1130
|
+
exports.restoreStdin = restoreStdin;
|
|
1131
|
+
exports.useMouse = useMouse;
|
|
1132
|
+
exports.useShortcuts = useShortcuts;
|
|
1133
|
+
exports.useTheme = useTheme;
|
|
1134
|
+
exports.useToast = useToast;
|
|
1135
|
+
exports.useTui = useTui;
|
|
1136
|
+
exports.useVirtualScroll = useVirtualScroll;
|
|
1137
|
+
//# sourceMappingURL=tui.cjs.map
|