multiple fixes

Signed-off-by: Hadi <[email protected]>
This commit is contained in:
Hadi
2026-09-01 16:35:10 +02:00
parent 038842878a
commit b329bdc074
19 changed files with 321 additions and 214 deletions
+2
View File
@@ -9,6 +9,7 @@
"zod": "^4.5.4", "zod": "^4.5.4",
}, },
"devDependencies": { "devDependencies": {
"@opentui/core": "^0.5.9",
"@opentui/keymap": "^0.5.9", "@opentui/keymap": "^0.5.9",
"@opentui/solid": "^0.5.9", "@opentui/solid": "^0.5.9",
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
@@ -24,6 +25,7 @@
"solid-js": ">=1.9.0", "solid-js": ">=1.9.0",
}, },
"optionalPeers": [ "optionalPeers": [
"@opentui/core",
"@opentui/keymap", "@opentui/keymap",
"@opentui/solid", "@opentui/solid",
"opentui-spinner", "opentui-spinner",
+1 -1
View File
@@ -1,7 +1,7 @@
// @bun // @bun
import { import {
Modal Modal
} from "./chunk-840y6ygm.js"; } from "./chunk-4j2gzwtd.js";
import { import {
helpOpen, helpOpen,
toggleHelp toggleHelp
+4 -2
View File
@@ -37,7 +37,7 @@ function Modal(props) {
_$setProp(_el$2, "paddingBottom", 1); _$setProp(_el$2, "paddingBottom", 1);
_$insert(_el$2, () => props.children); _$insert(_el$2, () => props.children);
_$effect((_p$) => { _$effect((_p$) => {
var _v$ = dimensions().width, _v$2 = dimensions().height, _v$3 = Math.floor(dimensions().height / 4), _v$4 = props.backdropColor ?? RGBA.fromInts(0, 0, 0, 150), _v$5 = theme.mouse ? props.onDismiss : undefined, _v$6 = props.width ?? 50, _v$7 = theme.borderStyle, _v$8 = props.accentColor ?? theme.primary, _v$9 = props.backgroundColor ?? theme.background; var _v$ = dimensions().width, _v$2 = dimensions().height, _v$3 = Math.floor(dimensions().height / 4), _v$4 = props.backdropColor ?? RGBA.fromInts(0, 0, 0, 150), _v$5 = theme.mouse ? props.onDismiss : undefined, _v$6 = props.width ?? 50, _v$7 = theme.borderStyle, _v$8 = props.accentColor ?? theme.primary, _v$9 = props.backgroundColor ?? theme.background, _v$0 = theme.mouse ? (event) => event.stopPropagation() : undefined;
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e)); _v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e));
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "height", _v$2, _p$.t)); _v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "height", _v$2, _p$.t));
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "paddingTop", _v$3, _p$.a)); _v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "paddingTop", _v$3, _p$.a));
@@ -47,6 +47,7 @@ function Modal(props) {
_v$7 !== _p$.s && (_p$.s = _$setProp(_el$2, "borderStyle", _v$7, _p$.s)); _v$7 !== _p$.s && (_p$.s = _$setProp(_el$2, "borderStyle", _v$7, _p$.s));
_v$8 !== _p$.h && (_p$.h = _$setProp(_el$2, "borderColor", _v$8, _p$.h)); _v$8 !== _p$.h && (_p$.h = _$setProp(_el$2, "borderColor", _v$8, _p$.h));
_v$9 !== _p$.r && (_p$.r = _$setProp(_el$2, "backgroundColor", _v$9, _p$.r)); _v$9 !== _p$.r && (_p$.r = _$setProp(_el$2, "backgroundColor", _v$9, _p$.r));
_v$0 !== _p$.d && (_p$.d = _$setProp(_el$2, "onMouseDown", _v$0, _p$.d));
return _p$; return _p$;
}, { }, {
e: undefined, e: undefined,
@@ -57,7 +58,8 @@ function Modal(props) {
n: undefined, n: undefined,
s: undefined, s: undefined,
h: undefined, h: undefined,
r: undefined r: undefined,
d: undefined
}); });
return _el$; return _el$;
} }
+29 -24
View File
@@ -4,7 +4,6 @@ import {
} from "./chunk-57mam43k.js"; } from "./chunk-57mam43k.js";
// src/components/Tabs.tsx // src/components/Tabs.tsx
import { createTextNode as _$createTextNode } from "@opentui/solid";
import { effect as _$effect } from "@opentui/solid"; import { effect as _$effect } from "@opentui/solid";
import { insertNode as _$insertNode } from "@opentui/solid"; import { insertNode as _$insertNode } from "@opentui/solid";
import { createComponent as _$createComponent } from "@opentui/solid"; import { createComponent as _$createComponent } from "@opentui/solid";
@@ -12,7 +11,7 @@ import { insert as _$insert } from "@opentui/solid";
import { use as _$use } from "@opentui/solid"; import { use as _$use } from "@opentui/solid";
import { setProp as _$setProp } from "@opentui/solid"; import { setProp as _$setProp } from "@opentui/solid";
import { createElement as _$createElement } from "@opentui/solid"; import { createElement as _$createElement } from "@opentui/solid";
import { CliRenderEvents, TextAttributes } from "@opentui/core"; import { BorderChars, CliRenderEvents, TextAttributes } from "@opentui/core";
import { onResize, useRenderer } from "@opentui/solid"; import { onResize, useRenderer } from "@opentui/solid";
import { createMemo, createSignal, For } from "solid-js"; import { createMemo, createSignal, For } from "solid-js";
function stepTabValue(items, value, delta) { function stepTabValue(items, value, delta) {
@@ -27,21 +26,21 @@ function innerWidth(label) {
function segmentWidth(label) { function segmentWidth(label) {
return innerWidth(label) + 2; return innerWidth(label) + 2;
} }
function topEdge(label) { function topEdge(chars, label) {
return `\u256D${"\u2500".repeat(innerWidth(label))}\u256E`; return `${chars.topLeft}${chars.horizontal.repeat(innerWidth(label))}${chars.topRight}`;
} }
function bottomEdge(label, isFirst, isLast, isActive) { function bottomEdge(chars, label, isFirst, isLast, isActive) {
const left = isFirst ? isActive ? "\u2502" : "\u251C" : isActive ? "\u256F" : "\u2534"; const left = isFirst ? isActive ? chars.vertical : chars.leftT : isActive ? chars.bottomRight : chars.bottomT;
const right = isLast ? isActive ? "\u2502" : "\u2524" : isActive ? "\u2570" : "\u2534"; const right = isLast ? isActive ? chars.vertical : chars.rightT : isActive ? chars.bottomLeft : chars.bottomT;
const fill = isActive ? " " : "\u2500"; const fill = isActive ? " " : chars.horizontal;
return left + fill.repeat(innerWidth(label)) + right; return left + fill.repeat(innerWidth(label)) + right;
} }
function buildBottomRow(segments, available) { function buildBottomRow(chars, segments, available) {
const tabsWidth = segments.reduce((sum, s) => sum + segmentWidth(s.label), 0); const tabsWidth = segments.reduce((sum, s) => sum + segmentWidth(s.label), 0);
const remainder = available - tabsWidth; const remainder = available - tabsWidth;
const extending = remainder > 0; const extending = remainder > 0;
const tabs = segments.map((s, i) => bottomEdge(s.label, i === 0, i === segments.length - 1 && !extending, s.isActive)).join(""); const tabs = segments.map((s, i) => bottomEdge(chars, s.label, i === 0, i === segments.length - 1 && !extending, s.isActive)).join("");
return extending ? tabs + "\u2500".repeat(remainder - 1) + "\u256E" : tabs; return extending ? tabs + chars.horizontal.repeat(remainder - 1) + chars.topRight : tabs;
} }
function fitCount(items, start, available) { function fitCount(items, start, available) {
const hiddenBefore = start; const hiddenBefore = start;
@@ -102,6 +101,7 @@ function Tabs(props) {
const accent = () => props.accentColor ?? theme.primary; const accent = () => props.accentColor ?? theme.primary;
const muted = () => props.mutedColor ?? theme.muted; const muted = () => props.mutedColor ?? theme.muted;
const borderColor = () => props.focused ? accent() : muted(); const borderColor = () => props.focused ? accent() : muted();
const chars = BorderChars[theme.borderStyle];
return (() => { return (() => {
var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("box"), _el$4 = _$createElement("text"); var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("box"), _el$4 = _$createElement("text");
_$insertNode(_el$, _el$2); _$insertNode(_el$, _el$2);
@@ -112,7 +112,8 @@ function Tabs(props) {
remeasureNextFrame(); remeasureNextFrame();
}, _el$); }, _el$);
_$setProp(_el$, "flexDirection", "column"); _$setProp(_el$, "flexDirection", "column");
_$insert(_el$2, () => segments().map((s) => topEdge(s.label)).join("")); _$setProp(_el$2, "selectable", false);
_$insert(_el$2, () => segments().map((s) => topEdge(chars, s.label)).join(""));
_$setProp(_el$3, "flexDirection", "row"); _$setProp(_el$3, "flexDirection", "row");
_$insert(_el$3, _$createComponent(For, { _$insert(_el$3, _$createComponent(For, {
get each() { get each() {
@@ -120,32 +121,36 @@ function Tabs(props) {
}, },
children: (s) => [(() => { children: (s) => [(() => {
var _el$5 = _$createElement("text"); var _el$5 = _$createElement("text");
_$insertNode(_el$5, _$createTextNode(`\u2502`)); _$setProp(_el$5, "selectable", false);
_$insert(_el$5, () => chars.vertical);
_$effect((_$p) => _$setProp(_el$5, "fg", borderColor(), _$p)); _$effect((_$p) => _$setProp(_el$5, "fg", borderColor(), _$p));
return _el$5; return _el$5;
})(), (() => { })(), (() => {
var _el$7 = _$createElement("text"); var _el$6 = _$createElement("text");
_$insert(_el$7, () => ` ${s.label} `); _$setProp(_el$6, "selectable", false);
_$insert(_el$6, () => ` ${s.label} `);
_$effect((_p$) => { _$effect((_p$) => {
var _v$3 = s.isActive ? accent() : muted(), _v$4 = s.isActive ? TextAttributes.BOLD : undefined, _v$5 = theme.mouse && s.value !== undefined ? () => props.onChange(s.value) : undefined; var _v$3 = s.isActive ? accent() : muted(), _v$4 = s.isActive ? TextAttributes.BOLD : undefined, _v$5 = theme.mouse && s.value !== undefined ? () => props.onChange(s.value) : undefined;
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$7, "fg", _v$3, _p$.e)); _v$3 !== _p$.e && (_p$.e = _$setProp(_el$6, "fg", _v$3, _p$.e));
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$7, "attributes", _v$4, _p$.t)); _v$4 !== _p$.t && (_p$.t = _$setProp(_el$6, "attributes", _v$4, _p$.t));
_v$5 !== _p$.a && (_p$.a = _$setProp(_el$7, "onMouseDown", _v$5, _p$.a)); _v$5 !== _p$.a && (_p$.a = _$setProp(_el$6, "onMouseDown", _v$5, _p$.a));
return _p$; return _p$;
}, { }, {
e: undefined, e: undefined,
t: undefined, t: undefined,
a: undefined a: undefined
}); });
return _el$7; return _el$6;
})(), (() => { })(), (() => {
var _el$8 = _$createElement("text"); var _el$7 = _$createElement("text");
_$insertNode(_el$8, _$createTextNode(`\u2502`)); _$setProp(_el$7, "selectable", false);
_$effect((_$p) => _$setProp(_el$8, "fg", borderColor(), _$p)); _$insert(_el$7, () => chars.vertical);
return _el$8; _$effect((_$p) => _$setProp(_el$7, "fg", borderColor(), _$p));
return _el$7;
})()] })()]
})); }));
_$insert(_el$4, () => buildBottomRow(segments(), width())); _$setProp(_el$4, "selectable", false);
_$insert(_el$4, () => buildBottomRow(chars, segments(), width()));
_$effect((_p$) => { _$effect((_p$) => {
var _v$ = borderColor(), _v$2 = borderColor(); var _v$ = borderColor(), _v$2 = borderColor();
_v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e)); _v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e));
+54
View File
@@ -0,0 +1,54 @@
// @bun
import {
theme
} from "./chunk-57mam43k.js";
// src/components/MinSizeGuard.tsx
import { createComponent as _$createComponent } from "@opentui/solid";
import { effect as _$effect } from "@opentui/solid";
import { insertNode as _$insertNode } from "@opentui/solid";
import { insert as _$insert } from "@opentui/solid";
import { setProp as _$setProp } from "@opentui/solid";
import { createElement as _$createElement } from "@opentui/solid";
import { memo as _$memo } from "@opentui/solid";
import { useTerminalDimensions } from "@opentui/solid";
import { Show } from "solid-js";
function MinSizeGuard(props) {
const dimensions = useTerminalDimensions();
const minWidth = () => props.minWidth ?? 80;
const minHeight = () => props.minHeight ?? 24;
const tooSmall = () => dimensions().width < minWidth() || dimensions().height < minHeight();
return [_$memo(() => props.children), _$createComponent(Show, {
get when() {
return tooSmall();
},
get children() {
var _el$ = _$createElement("box"), _el$2 = _$createElement("text");
_$insertNode(_el$, _el$2);
_$setProp(_el$, "position", "absolute");
_$setProp(_el$, "top", 0);
_$setProp(_el$, "left", 0);
_$setProp(_el$, "alignItems", "center");
_$setProp(_el$, "justifyContent", "center");
_$setProp(_el$, "zIndex", 9999);
_$insert(_el$2, () => `Terminal too small
Minimum size: ${minWidth()}x${minHeight()} \u2014 current: ${dimensions().width}x${dimensions().height}`);
_$effect((_p$) => {
var _v$ = dimensions().width, _v$2 = dimensions().height, _v$3 = props.backgroundColor ?? theme.background, _v$4 = props.textColor ?? theme.muted;
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e));
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "height", _v$2, _p$.t));
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "backgroundColor", _v$3, _p$.a));
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$2, "fg", _v$4, _p$.o));
return _p$;
}, {
e: undefined,
t: undefined,
a: undefined,
o: undefined
});
return _el$;
}
})];
}
export { MinSizeGuard };
-60
View File
@@ -1,60 +0,0 @@
// @bun
import {
theme
} from "./chunk-57mam43k.js";
// src/components/MinSizeGuard.tsx
import { createComponent as _$createComponent } from "@opentui/solid";
import { effect as _$effect } from "@opentui/solid";
import { insertNode as _$insertNode } from "@opentui/solid";
import { insert as _$insert } from "@opentui/solid";
import { setProp as _$setProp } from "@opentui/solid";
import { createElement as _$createElement } from "@opentui/solid";
import { useTerminalDimensions } from "@opentui/solid";
import { Match, Switch } from "solid-js";
function MinSizeGuard(props) {
const dimensions = useTerminalDimensions();
const minWidth = () => props.minWidth ?? 80;
const minHeight = () => props.minHeight ?? 24;
const tooSmall = () => dimensions().width < minWidth() || dimensions().height < minHeight();
return _$createComponent(Switch, {
get children() {
return [_$createComponent(Match, {
get when() {
return tooSmall();
},
get children() {
var _el$ = _$createElement("box"), _el$2 = _$createElement("text");
_$insertNode(_el$, _el$2);
_$setProp(_el$, "alignItems", "center");
_$setProp(_el$, "justifyContent", "center");
_$insert(_el$2, () => `Terminal too small
Minimum size: ${minWidth()}x${minHeight()} \u2014 current: ${dimensions().width}x${dimensions().height}`);
_$effect((_p$) => {
var _v$ = dimensions().width, _v$2 = dimensions().height, _v$3 = props.backgroundColor ?? theme.background, _v$4 = props.textColor ?? theme.muted;
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e));
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "height", _v$2, _p$.t));
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "backgroundColor", _v$3, _p$.a));
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$2, "fg", _v$4, _p$.o));
return _p$;
}, {
e: undefined,
t: undefined,
a: undefined,
o: undefined
});
return _el$;
}
}), _$createComponent(Match, {
get when() {
return !tooSmall();
},
get children() {
return props.children;
}
})];
}
});
}
export { MinSizeGuard };
+128
View File
@@ -0,0 +1,128 @@
// @bun
import {
presets,
theme
} from "./chunk-57mam43k.js";
// src/components/Sidebar.tsx
import { use as _$use } from "@opentui/solid";
import { createComponent as _$createComponent } from "@opentui/solid";
import { createTextNode as _$createTextNode } from "@opentui/solid";
import { insertNode as _$insertNode } from "@opentui/solid";
import { effect as _$effect } from "@opentui/solid";
import { insert as _$insert } from "@opentui/solid";
import { setProp as _$setProp } from "@opentui/solid";
import { createElement as _$createElement } from "@opentui/solid";
import { TextAttributes } from "@opentui/core";
import { Show } from "solid-js";
function itemIndexAtScreenY(el, screenY) {
const internals = el;
if (!internals.linesPerItem)
return null;
const localY = screenY - el.screenY;
if (localY < 0)
return null;
const index = internals.scrollOffset + Math.floor(localY / internals.linesPerItem);
return index >= 0 && index < el.options.length ? index : null;
}
function Sidebar(props) {
let select;
const handleMouseDown = (event) => {
if (!select)
return;
const index = itemIndexAtScreenY(select, event.y);
if (index !== null)
select.setSelectedIndex(index);
};
const handleMouseScroll = (event) => {
if (!select || !event.scroll)
return;
const steps = Math.max(1, event.scroll.delta);
if (event.scroll.direction === "down")
select.moveDown(steps);
else if (event.scroll.direction === "up")
select.moveUp(steps);
};
return (() => {
var _el$ = _$createElement("box"), _el$5 = _$createElement("select");
_$insertNode(_el$, _el$5);
_$setProp(_el$, "border", true);
_$setProp(_el$, "flexDirection", "column");
_$insert(_el$, _$createComponent(Show, {
get when() {
return props.title;
},
get children() {
return [(() => {
var _el$2 = _$createElement("text");
_$insert(_el$2, () => props.title);
_$effect((_$p) => _$setProp(_el$2, "attributes", TextAttributes.BOLD, _$p));
return _el$2;
})(), (() => {
var _el$3 = _$createElement("text");
_$insertNode(_el$3, _$createTextNode(` `));
return _el$3;
})()];
}
}), _el$5);
_$use((el) => {
select = el ?? undefined;
props.ref?.(el && {
moveUp: () => el.moveUp(),
moveDown: () => el.moveDown(),
selectCurrent: () => el.selectCurrent(),
focus: () => el.focus()
});
}, _el$5);
_$setProp(_el$5, "flexGrow", 1);
_$setProp(_el$5, "showScrollIndicator", true);
_$setProp(_el$5, "onChange", (_index, option) => {
if (!option)
return;
props.onSelect(option);
});
_$setProp(_el$5, "onSelect", (_index, option) => {
if (!option)
return;
props.onConfirm?.(option);
});
_$effect((_p$) => {
var _v$ = props.width ?? 24, _v$2 = theme.borderStyle, _v$3 = props.focused ? props.accentColor ?? theme.primary : props.mutedColor ?? theme.muted, _v$4 = props.focused, _v$5 = props.items, _v$6 = theme.mouse ? handleMouseDown : undefined, _v$7 = theme.mouse ? handleMouseScroll : undefined, _v$8 = props.backgroundColor ?? "transparent", _v$9 = props.textColor ?? presets.select.textColor, _v$0 = props.focusedBackgroundColor ?? "transparent", _v$1 = props.focusedTextColor ?? presets.select.focusedTextColor, _v$10 = props.selectedBackgroundColor ?? presets.select.selectedBackgroundColor, _v$11 = props.selectedTextColor ?? presets.select.selectedTextColor, _v$12 = props.descriptionColor ?? presets.select.descriptionColor, _v$13 = props.selectedDescriptionColor ?? presets.select.selectedDescriptionColor;
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e));
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "borderStyle", _v$2, _p$.t));
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "borderColor", _v$3, _p$.a));
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$5, "focused", _v$4, _p$.o));
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$5, "options", _v$5, _p$.i));
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$5, "onMouseDown", _v$6, _p$.n));
_v$7 !== _p$.s && (_p$.s = _$setProp(_el$5, "onMouseScroll", _v$7, _p$.s));
_v$8 !== _p$.h && (_p$.h = _$setProp(_el$5, "backgroundColor", _v$8, _p$.h));
_v$9 !== _p$.r && (_p$.r = _$setProp(_el$5, "textColor", _v$9, _p$.r));
_v$0 !== _p$.d && (_p$.d = _$setProp(_el$5, "focusedBackgroundColor", _v$0, _p$.d));
_v$1 !== _p$.l && (_p$.l = _$setProp(_el$5, "focusedTextColor", _v$1, _p$.l));
_v$10 !== _p$.u && (_p$.u = _$setProp(_el$5, "selectedBackgroundColor", _v$10, _p$.u));
_v$11 !== _p$.c && (_p$.c = _$setProp(_el$5, "selectedTextColor", _v$11, _p$.c));
_v$12 !== _p$.w && (_p$.w = _$setProp(_el$5, "descriptionColor", _v$12, _p$.w));
_v$13 !== _p$.m && (_p$.m = _$setProp(_el$5, "selectedDescriptionColor", _v$13, _p$.m));
return _p$;
}, {
e: undefined,
t: undefined,
a: undefined,
o: undefined,
i: undefined,
n: undefined,
s: undefined,
h: undefined,
r: undefined,
d: undefined,
l: undefined,
u: undefined,
c: undefined,
w: undefined,
m: undefined
});
return _el$;
})();
}
export { Sidebar };
-78
View File
@@ -1,78 +0,0 @@
// @bun
import {
theme
} from "./chunk-57mam43k.js";
// src/components/Sidebar.tsx
import { effect as _$effect } from "@opentui/solid";
import { insertNode as _$insertNode } from "@opentui/solid";
import { use as _$use } from "@opentui/solid";
import { insert as _$insert } from "@opentui/solid";
import { setProp as _$setProp } from "@opentui/solid";
import { createElement as _$createElement } from "@opentui/solid";
import { TextAttributes } from "@opentui/core";
function Sidebar(props) {
return (() => {
var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("select");
_$insertNode(_el$, _el$2);
_$insertNode(_el$, _el$3);
_$setProp(_el$, "border", true);
_$setProp(_el$, "flexDirection", "column");
_$insert(_el$2, () => props.title);
_$use((el) => {
props.ref?.(el && {
moveUp: () => el.moveUp(),
moveDown: () => el.moveDown(),
selectCurrent: () => el.selectCurrent()
});
}, _el$3);
_$setProp(_el$3, "flexGrow", 1);
_$setProp(_el$3, "showScrollIndicator", true);
_$setProp(_el$3, "onChange", (_index, option) => {
if (!option)
return;
props.onSelect(option);
});
_$setProp(_el$3, "onSelect", (_index, option) => {
if (!option)
return;
props.onConfirm?.(option);
});
_$effect((_p$) => {
var _v$ = props.width ?? 24, _v$2 = theme.borderStyle, _v$3 = props.focused ? props.accentColor ?? theme.primary : props.mutedColor ?? theme.muted, _v$4 = TextAttributes.BOLD, _v$5 = props.focused, _v$6 = props.items, _v$7 = props.backgroundColor, _v$8 = props.textColor, _v$9 = props.focusedBackgroundColor, _v$0 = props.focusedTextColor, _v$1 = props.selectedBackgroundColor, _v$10 = props.selectedTextColor, _v$11 = props.descriptionColor, _v$12 = props.selectedDescriptionColor;
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e));
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "borderStyle", _v$2, _p$.t));
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "borderColor", _v$3, _p$.a));
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$2, "attributes", _v$4, _p$.o));
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$3, "focused", _v$5, _p$.i));
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$3, "options", _v$6, _p$.n));
_v$7 !== _p$.s && (_p$.s = _$setProp(_el$3, "backgroundColor", _v$7, _p$.s));
_v$8 !== _p$.h && (_p$.h = _$setProp(_el$3, "textColor", _v$8, _p$.h));
_v$9 !== _p$.r && (_p$.r = _$setProp(_el$3, "focusedBackgroundColor", _v$9, _p$.r));
_v$0 !== _p$.d && (_p$.d = _$setProp(_el$3, "focusedTextColor", _v$0, _p$.d));
_v$1 !== _p$.l && (_p$.l = _$setProp(_el$3, "selectedBackgroundColor", _v$1, _p$.l));
_v$10 !== _p$.u && (_p$.u = _$setProp(_el$3, "selectedTextColor", _v$10, _p$.u));
_v$11 !== _p$.c && (_p$.c = _$setProp(_el$3, "descriptionColor", _v$11, _p$.c));
_v$12 !== _p$.w && (_p$.w = _$setProp(_el$3, "selectedDescriptionColor", _v$12, _p$.w));
return _p$;
}, {
e: undefined,
t: undefined,
a: undefined,
o: undefined,
i: undefined,
n: undefined,
s: undefined,
h: undefined,
r: undefined,
d: undefined,
l: undefined,
u: undefined,
c: undefined,
w: undefined
});
return _el$;
})();
}
export { Sidebar };
+2 -2
View File
@@ -1,8 +1,8 @@
// @bun // @bun
import { import {
HelpModal HelpModal
} from "../chunk-ydf2rnsn.js"; } from "../chunk-2n7bvge9.js";
import"../chunk-840y6ygm.js"; import"../chunk-4j2gzwtd.js";
import"../chunk-57mam43k.js"; import"../chunk-57mam43k.js";
import"../chunk-ergc99dh.js"; import"../chunk-ergc99dh.js";
import"../chunk-7f8jagy5.js"; import"../chunk-7f8jagy5.js";
+1 -1
View File
@@ -1,7 +1,7 @@
// @bun // @bun
import { import {
MinSizeGuard MinSizeGuard
} from "../chunk-syn54rcq.js"; } from "../chunk-nr74cgcr.js";
import"../chunk-57mam43k.js"; import"../chunk-57mam43k.js";
import"../chunk-ergc99dh.js"; import"../chunk-ergc99dh.js";
import"../chunk-7f8jagy5.js"; import"../chunk-7f8jagy5.js";
+1 -1
View File
@@ -1,7 +1,7 @@
// @bun // @bun
import { import {
Modal Modal
} from "../chunk-840y6ygm.js"; } from "../chunk-4j2gzwtd.js";
import"../chunk-57mam43k.js"; import"../chunk-57mam43k.js";
import"../chunk-ergc99dh.js"; import"../chunk-ergc99dh.js";
import"../chunk-7f8jagy5.js"; import"../chunk-7f8jagy5.js";
+1 -1
View File
@@ -1,7 +1,7 @@
// @bun // @bun
import { import {
Sidebar Sidebar
} from "../chunk-wkg33t3z.js"; } from "../chunk-vve1k0ax.js";
import"../chunk-57mam43k.js"; import"../chunk-57mam43k.js";
import"../chunk-ergc99dh.js"; import"../chunk-ergc99dh.js";
import"../chunk-7f8jagy5.js"; import"../chunk-7f8jagy5.js";
+1 -1
View File
@@ -2,7 +2,7 @@
import { import {
Tabs, Tabs,
stepTabValue stepTabValue
} from "../chunk-d2tt920p.js"; } from "../chunk-4jy6s3k0.js";
import"../chunk-57mam43k.js"; import"../chunk-57mam43k.js";
import"../chunk-ergc99dh.js"; import"../chunk-ergc99dh.js";
import"../chunk-7f8jagy5.js"; import"../chunk-7f8jagy5.js";
+2 -2
View File
@@ -1,8 +1,8 @@
// @bun // @bun
import { import {
HelpModal HelpModal
} from "../chunk-ydf2rnsn.js"; } from "../chunk-2n7bvge9.js";
import"../chunk-840y6ygm.js"; import"../chunk-4j2gzwtd.js";
import { import {
HelpBar HelpBar
} from "../chunk-dv2w3sfk.js"; } from "../chunk-dv2w3sfk.js";
+8 -8
View File
@@ -1,10 +1,7 @@
// @bun // @bun
import {
ProgressBar
} from "../chunk-ae0jkf2z.js";
import { import {
Sidebar Sidebar
} from "../chunk-wkg33t3z.js"; } from "../chunk-vve1k0ax.js";
import { import {
SignalBars SignalBars
} from "../chunk-xab9ag99.js"; } from "../chunk-xab9ag99.js";
@@ -14,19 +11,22 @@ import {
import { import {
Toggle Toggle
} from "../chunk-zs53wnet.js"; } from "../chunk-zs53wnet.js";
import {
MinSizeGuard
} from "../chunk-nr74cgcr.js";
import { import {
Tabs, Tabs,
stepTabValue stepTabValue
} from "../chunk-d2tt920p.js"; } from "../chunk-4jy6s3k0.js";
import { import {
Modal Modal
} from "../chunk-840y6ygm.js"; } from "../chunk-4j2gzwtd.js";
import { import {
Badge Badge
} from "../chunk-shxcerba.js"; } from "../chunk-shxcerba.js";
import { import {
MinSizeGuard ProgressBar
} from "../chunk-syn54rcq.js"; } from "../chunk-ae0jkf2z.js";
import"../chunk-57mam43k.js"; import"../chunk-57mam43k.js";
import"../chunk-ergc99dh.js"; import"../chunk-ergc99dh.js";
import"../chunk-7f8jagy5.js"; import"../chunk-7f8jagy5.js";
+10 -6
View File
@@ -1,6 +1,6 @@
import type { ColorInput } from "@opentui/core"; import type { ColorInput } from "@opentui/core";
import { useTerminalDimensions } from "@opentui/solid"; import { useTerminalDimensions } from "@opentui/solid";
import { Match, Switch, type ParentProps } from "solid-js"; import { Show, type ParentProps } from "solid-js";
import { theme } from "../index.ts"; import { theme } from "../index.ts";
export interface MinSizeGuardProps extends ParentProps { export interface MinSizeGuardProps extends ParentProps {
@@ -17,21 +17,25 @@ export function MinSizeGuard(props: MinSizeGuardProps) {
const tooSmall = () => dimensions().width < minWidth() || dimensions().height < minHeight(); const tooSmall = () => dimensions().width < minWidth() || dimensions().height < minHeight();
return ( return (
<Switch> <>
<Match when={tooSmall()}> {props.children}
<Show when={tooSmall()}>
<box <box
position="absolute"
top={0}
left={0}
width={dimensions().width} width={dimensions().width}
height={dimensions().height} height={dimensions().height}
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
backgroundColor={props.backgroundColor ?? theme.background} backgroundColor={props.backgroundColor ?? theme.background}
zIndex={9999}
> >
<text fg={props.textColor ?? theme.muted}> <text fg={props.textColor ?? theme.muted}>
{`Terminal too small\nMinimum size: ${minWidth()}x${minHeight()} — current: ${dimensions().width}x${dimensions().height}`} {`Terminal too small\nMinimum size: ${minWidth()}x${minHeight()} — current: ${dimensions().width}x${dimensions().height}`}
</text> </text>
</box> </box>
</Match> </Show>
<Match when={!tooSmall()}>{props.children}</Match> </>
</Switch>
); );
} }
+2 -1
View File
@@ -1,4 +1,4 @@
import { type ColorInput, RGBA } from "@opentui/core"; import { type ColorInput, type MouseEvent, RGBA } from "@opentui/core";
import { Portal, useTerminalDimensions } from "@opentui/solid"; import { Portal, useTerminalDimensions } from "@opentui/solid";
import { Show, type ParentProps } from "solid-js"; import { Show, type ParentProps } from "solid-js";
import { theme } from "../index.ts"; import { theme } from "../index.ts";
@@ -41,6 +41,7 @@ export function Modal(props: ModalProps) {
paddingRight={2} paddingRight={2}
paddingTop={1} paddingTop={1}
paddingBottom={1} paddingBottom={1}
onMouseDown={theme.mouse ? (event: MouseEvent) => event.stopPropagation() : undefined}
> >
{props.children} {props.children}
</box> </box>
+44 -11
View File
@@ -1,14 +1,25 @@
import { type ColorInput, type SelectOption, TextAttributes } from "@opentui/core"; import { type ColorInput, type MouseEvent, type SelectOption, type SelectRenderable, TextAttributes } from "@opentui/core";
import { theme } from "../index.ts"; import { Show } from "solid-js";
import { presets, theme } from "../index.ts";
export interface SidebarHandle { export interface SidebarHandle {
moveUp(): void; moveUp(): void;
moveDown(): void; moveDown(): void;
selectCurrent(): void; selectCurrent(): void;
focus(): void;
}
function itemIndexAtScreenY(el: SelectRenderable, screenY: number): number | null {
const internals = el as unknown as { scrollOffset: number; linesPerItem: number };
if (!internals.linesPerItem) return null;
const localY = screenY - el.screenY;
if (localY < 0) return null;
const index = internals.scrollOffset + Math.floor(localY / internals.linesPerItem);
return index >= 0 && index < el.options.length ? index : null;
} }
export interface SidebarProps { export interface SidebarProps {
title: string; title?: string;
items: SelectOption[]; items: SelectOption[];
width?: number; width?: number;
focused?: boolean; focused?: boolean;
@@ -30,6 +41,21 @@ export interface SidebarProps {
} }
export function Sidebar(props: SidebarProps) { export function Sidebar(props: SidebarProps) {
let select: SelectRenderable | undefined;
const handleMouseDown = (event: MouseEvent) => {
if (!select) return;
const index = itemIndexAtScreenY(select, event.y);
if (index !== null) select.setSelectedIndex(index);
};
const handleMouseScroll = (event: MouseEvent) => {
if (!select || !event.scroll) return;
const steps = Math.max(1, event.scroll.delta);
if (event.scroll.direction === "down") select.moveDown(steps);
else if (event.scroll.direction === "up") select.moveUp(steps);
};
return ( return (
<box <box
width={props.width ?? 24} width={props.width ?? 24}
@@ -38,14 +64,19 @@ export function Sidebar(props: SidebarProps) {
borderColor={props.focused ? (props.accentColor ?? theme.primary) : (props.mutedColor ?? theme.muted)} borderColor={props.focused ? (props.accentColor ?? theme.primary) : (props.mutedColor ?? theme.muted)}
flexDirection="column" flexDirection="column"
> >
<Show when={props.title}>
<text attributes={TextAttributes.BOLD}>{props.title}</text> <text attributes={TextAttributes.BOLD}>{props.title}</text>
<text> </text>
</Show>
<select <select
ref={(el) => { ref={(el) => {
select = el ?? undefined;
props.ref?.( props.ref?.(
el && { el && {
moveUp: () => el.moveUp(), moveUp: () => el.moveUp(),
moveDown: () => el.moveDown(), moveDown: () => el.moveDown(),
selectCurrent: () => el.selectCurrent(), selectCurrent: () => el.selectCurrent(),
focus: () => el.focus(),
}, },
); );
}} }}
@@ -53,14 +84,16 @@ export function Sidebar(props: SidebarProps) {
focused={props.focused} focused={props.focused}
showScrollIndicator showScrollIndicator
options={props.items} options={props.items}
backgroundColor={props.backgroundColor} onMouseDown={theme.mouse ? handleMouseDown : undefined}
textColor={props.textColor} onMouseScroll={theme.mouse ? handleMouseScroll : undefined}
focusedBackgroundColor={props.focusedBackgroundColor} backgroundColor={props.backgroundColor ?? "transparent"}
focusedTextColor={props.focusedTextColor} textColor={props.textColor ?? presets.select.textColor}
selectedBackgroundColor={props.selectedBackgroundColor} focusedBackgroundColor={props.focusedBackgroundColor ?? "transparent"}
selectedTextColor={props.selectedTextColor} focusedTextColor={props.focusedTextColor ?? presets.select.focusedTextColor}
descriptionColor={props.descriptionColor} selectedBackgroundColor={props.selectedBackgroundColor ?? presets.select.selectedBackgroundColor}
selectedDescriptionColor={props.selectedDescriptionColor} selectedTextColor={props.selectedTextColor ?? presets.select.selectedTextColor}
descriptionColor={props.descriptionColor ?? presets.select.descriptionColor}
selectedDescriptionColor={props.selectedDescriptionColor ?? presets.select.selectedDescriptionColor}
onChange={(_index, option) => { onChange={(_index, option) => {
if (!option) return; if (!option) return;
props.onSelect(option); props.onSelect(option);
+30 -14
View File
@@ -1,4 +1,4 @@
import { type ColorInput, CliRenderEvents, TextAttributes, type BoxRenderable } from "@opentui/core"; import { type BorderCharacters, type BoxRenderable, type ColorInput, BorderChars, CliRenderEvents, TextAttributes } from "@opentui/core";
import { onResize, useRenderer } from "@opentui/solid"; import { onResize, useRenderer } from "@opentui/solid";
import { createMemo, createSignal, For } from "solid-js"; import { createMemo, createSignal, For } from "solid-js";
import { theme } from "../index.ts"; import { theme } from "../index.ts";
@@ -31,27 +31,31 @@ function segmentWidth(label: string): number {
return innerWidth(label) + 2; return innerWidth(label) + 2;
} }
function topEdge(label: string): string { function topEdge(chars: BorderCharacters, label: string): string {
return `╭${"─".repeat(innerWidth(label))}╮`; return `${chars.topLeft}${chars.horizontal.repeat(innerWidth(label))}${chars.topRight}`;
} }
function bottomEdge(label: string, isFirst: boolean, isLast: boolean, isActive: boolean): string { function bottomEdge(chars: BorderCharacters, label: string, isFirst: boolean, isLast: boolean, isActive: boolean): string {
const left = isFirst ? (isActive ? "│" : "├") : isActive ? "╯" : "┴"; const left = isFirst ? (isActive ? chars.vertical : chars.leftT) : isActive ? chars.bottomRight : chars.bottomT;
const right = isLast ? (isActive ? "│" : "┤") : isActive ? "╰" : "┴"; const right = isLast ? (isActive ? chars.vertical : chars.rightT) : isActive ? chars.bottomLeft : chars.bottomT;
const fill = isActive ? " " : "─"; const fill = isActive ? " " : chars.horizontal;
return left + fill.repeat(innerWidth(label)) + right; return left + fill.repeat(innerWidth(label)) + right;
} }
function buildBottomRow(segments: { label: string; isActive: boolean }[], available: number): string { function buildBottomRow(
chars: BorderCharacters,
segments: { label: string; isActive: boolean }[],
available: number,
): string {
const tabsWidth = segments.reduce((sum, s) => sum + segmentWidth(s.label), 0); const tabsWidth = segments.reduce((sum, s) => sum + segmentWidth(s.label), 0);
const remainder = available - tabsWidth; const remainder = available - tabsWidth;
const extending = remainder > 0; const extending = remainder > 0;
const tabs = segments const tabs = segments
.map((s, i) => bottomEdge(s.label, i === 0, i === segments.length - 1 && !extending, s.isActive)) .map((s, i) => bottomEdge(chars, s.label, i === 0, i === segments.length - 1 && !extending, s.isActive))
.join(""); .join("");
return extending ? tabs + "─".repeat(remainder - 1) + "╮" : tabs; return extending ? tabs + chars.horizontal.repeat(remainder - 1) + chars.topRight : tabs;
} }
function fitCount(items: TabItem[], start: number, available: number): number { function fitCount(items: TabItem[], start: number, available: number): number {
@@ -117,6 +121,7 @@ export function Tabs(props: TabsProps) {
const muted = () => props.mutedColor ?? theme.muted; const muted = () => props.mutedColor ?? theme.muted;
const borderColor = () => (props.focused ? accent() : muted()); const borderColor = () => (props.focused ? accent() : muted());
const chars = BorderChars[theme.borderStyle];
return ( return (
<box <box
@@ -126,25 +131,36 @@ export function Tabs(props: TabsProps) {
remeasureNextFrame(); remeasureNextFrame();
}} }}
> >
<text fg={borderColor()}>{segments().map((s) => topEdge(s.label)).join("")}</text> <text selectable={false} fg={borderColor()}>
{segments()
.map((s) => topEdge(chars, s.label))
.join("")}
</text>
<box flexDirection="row"> <box flexDirection="row">
<For each={segments()}> <For each={segments()}>
{(s) => ( {(s) => (
<> <>
<text fg={borderColor()}>│</text> <text selectable={false} fg={borderColor()}>
{chars.vertical}
</text>
<text <text
selectable={false}
fg={s.isActive ? accent() : muted()} fg={s.isActive ? accent() : muted()}
attributes={s.isActive ? TextAttributes.BOLD : undefined} attributes={s.isActive ? TextAttributes.BOLD : undefined}
onMouseDown={theme.mouse && s.value !== undefined ? () => props.onChange(s.value!) : undefined} onMouseDown={theme.mouse && s.value !== undefined ? () => props.onChange(s.value!) : undefined}
> >
{` ${s.label} `} {` ${s.label} `}
</text> </text>
<text fg={borderColor()}>│</text> <text selectable={false} fg={borderColor()}>
{chars.vertical}
</text>
</> </>
)} )}
</For> </For>
</box> </box>
<text fg={borderColor()}>{buildBottomRow(segments(), width())}</text> <text selectable={false} fg={borderColor()}>
{buildBottomRow(chars, segments(), width())}
</text>
</box> </box>
); );
} }