mirror of
https://github.com/anotherhadi/ilovetui.git
synced 2026-10-05 10:58:26 +02:00
@@ -17,6 +17,13 @@ import { theme, presets } from "ilovetui";
|
||||
import "ilovetui/solid";
|
||||
```
|
||||
|
||||
Entry points: `.`, `./solid`, `./context`, `./project-config`, `./keymap`, `./components`, `./components/help`, `./components/spinner`. See the source for what each exports.
|
||||
Entry points: `.`, `./solid`, `./context`, `./project-config`, `./keymap`, `./components`, `./components/help`, `./components/spinner`, `./components/section-header`. See the source for what each exports.
|
||||
|
||||
The theme is base16 and user-overridable via `~/.config/ilovetui/config.yaml`. Copy [`src/default.yaml`](https://github.com/anotherhadi/ilovetui/blob/main/src/default.yaml) there as a starting point.
|
||||
|
||||
An app can layer its own theme overrides on top of that file (e.g. a `theme:` section of its own config, validated with `ThemeConfigSchema`) by calling `configureTheme(overrides)` once at startup, before the first render. Invalid theme files are reported as warning toasts and skipped; unknown keys are ignored, so other TUIs can keep their own keys in the shared file.
|
||||
|
||||
## Focus and keymap scoping
|
||||
|
||||
Wrap each focus region in a `<Pane>` (`focusable` when the pane itself is the focus target). Inside it, `usePane()` gives `target` — pass it to `useBindings` so the layer is only active while focus is within the pane — and `focused()` for styling. `<Modal>` takes focus while open, restores it on close, and binds its `dismissKey` (default `escape`) plus any `commands`/`bindings` you pass, scoped to itself. Note that keymap layers are ordered by `priority` then registration order, not by tree depth: keep overlapping layers on disjoint targets, or give them explicit priorities.
|
||||
|
||||
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
// @bun
|
||||
// src/context/focus.ts
|
||||
import { CliRenderEvents } from "@opentui/core";
|
||||
import { useRenderer } from "@opentui/solid";
|
||||
import { createEffect, createSignal, onCleanup } from "solid-js";
|
||||
function isInside2(node, root) {
|
||||
if (!root)
|
||||
return false;
|
||||
for (let current = node;current; current = current.parent) {
|
||||
if (current === root)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function useFocusWithin2(target) {
|
||||
const renderer = useRenderer();
|
||||
const [focused, setFocused] = createSignal(false);
|
||||
const sync = () => setFocused(isInside2(renderer.currentFocusedRenderable, target() ?? null));
|
||||
renderer.on(CliRenderEvents.FOCUSED_RENDERABLE, sync);
|
||||
onCleanup(() => renderer.off(CliRenderEvents.FOCUSED_RENDERABLE, sync));
|
||||
createEffect(sync);
|
||||
return focused;
|
||||
}
|
||||
|
||||
export { isInside2, useFocusWithin2 };
|
||||
+5
-5
@@ -2,20 +2,20 @@
|
||||
// src/context/notifications.ts
|
||||
import { createSignal } from "solid-js";
|
||||
var DEFAULT_DURATION = 3000;
|
||||
var [notifications, setNotifications] = createSignal([]);
|
||||
var [notifications2, setNotifications] = createSignal([]);
|
||||
var nextId = 0;
|
||||
function notify(message, options = {}) {
|
||||
function notify2(message, options = {}) {
|
||||
const id = options.id ?? `toast-${nextId++}`;
|
||||
const toast = { id, title: options.title, message, kind: options.kind ?? "info" };
|
||||
setNotifications((prev) => [...prev.filter((n) => n.id !== id), toast]);
|
||||
const duration = options.duration ?? DEFAULT_DURATION;
|
||||
if (duration > 0) {
|
||||
setTimeout(() => dismiss(id), duration);
|
||||
setTimeout(() => dismiss2(id), duration);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
function dismiss(id) {
|
||||
function dismiss2(id) {
|
||||
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
||||
}
|
||||
|
||||
export { notifications, notify, dismiss };
|
||||
export { notifications2, notify2, dismiss2 };
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
// @bun
|
||||
// src/yaml.ts
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { join } from "path";
|
||||
import { parse as parseYAML } from "yaml";
|
||||
function configPath() {
|
||||
const configDir = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
||||
return join(configDir, "ilovetui", "config.yaml");
|
||||
}
|
||||
function readYamlFile(path) {
|
||||
if (!existsSync(path))
|
||||
return;
|
||||
try {
|
||||
return parseYAML(readFileSync(path, "utf8")) ?? undefined;
|
||||
} catch (err) {
|
||||
console.error(`ilovetui: failed to parse ${path}, ignoring it.`, err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export { configPath, readYamlFile };
|
||||
+5
-5
@@ -1,23 +1,23 @@
|
||||
// @bun
|
||||
import {
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/Box.tsx
|
||||
import { spread as _$spread } from "@opentui/solid";
|
||||
import { mergeProps as _$mergeProps } from "@opentui/solid";
|
||||
import { memo as _$memo } from "@opentui/solid";
|
||||
import { createElement as _$createElement } from "@opentui/solid";
|
||||
function Box(props) {
|
||||
function Box2(props) {
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box");
|
||||
_$spread(_el$, _$mergeProps(props, {
|
||||
get borderStyle() {
|
||||
return props.borderStyle ?? theme.borderStyle;
|
||||
return props.borderStyle ?? theme2.borderStyle;
|
||||
}
|
||||
}), false);
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
export { Box };
|
||||
export { Box2 };
|
||||
+13
-13
@@ -1,11 +1,11 @@
|
||||
// @bun
|
||||
import {
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
notifications2,
|
||||
dismiss2
|
||||
} from "./chunk-6ggk5fg7.js";
|
||||
import {
|
||||
dismiss,
|
||||
notifications
|
||||
} from "./chunk-y6txxzxk.js";
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/NotificationHost.tsx
|
||||
import { effect as _$effect } from "@opentui/solid";
|
||||
@@ -23,12 +23,12 @@ var KIND_LABEL = {
|
||||
warning: "Warning",
|
||||
error: "Error"
|
||||
};
|
||||
function NotificationHost(props = {}) {
|
||||
function NotificationHost2(props = {}) {
|
||||
const defaultColor = () => ({
|
||||
info: theme.primary,
|
||||
success: theme.success,
|
||||
warning: theme.warning,
|
||||
error: theme.error
|
||||
info: theme2.primary,
|
||||
success: theme2.success,
|
||||
warning: theme2.warning,
|
||||
error: theme2.error
|
||||
});
|
||||
const colorFor = (kind) => props.colors?.[kind] ?? defaultColor()[kind];
|
||||
return _$createComponent(Portal, {
|
||||
@@ -41,7 +41,7 @@ function NotificationHost(props = {}) {
|
||||
_$setProp(_el$, "zIndex", 1000);
|
||||
_$insert(_el$, _$createComponent(For, {
|
||||
get each() {
|
||||
return notifications();
|
||||
return notifications2();
|
||||
},
|
||||
children: (toast) => (() => {
|
||||
var _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("text");
|
||||
@@ -52,7 +52,7 @@ function NotificationHost(props = {}) {
|
||||
_$insert(_el$3, () => toast.title ?? KIND_LABEL[toast.kind]);
|
||||
_$insert(_el$4, () => toast.message);
|
||||
_$effect((_p$) => {
|
||||
var _v$ = theme.borderStyle, _v$2 = colorFor(toast.kind), _v$3 = props.backgroundColor ?? theme.background, _v$4 = props.width ?? 32, _v$5 = theme.mouse ? () => dismiss(toast.id) : undefined, _v$6 = colorFor(toast.kind);
|
||||
var _v$ = theme2.borderStyle, _v$2 = colorFor(toast.kind), _v$3 = props.backgroundColor ?? theme2.background, _v$4 = props.width ?? 32, _v$5 = theme2.mouse ? () => dismiss2(toast.id) : undefined, _v$6 = colorFor(toast.kind);
|
||||
_v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "borderStyle", _v$, _p$.e));
|
||||
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "borderColor", _v$2, _p$.t));
|
||||
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$2, "backgroundColor", _v$3, _p$.a));
|
||||
@@ -76,4 +76,4 @@ function NotificationHost(props = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
export { NotificationHost };
|
||||
export { NotificationHost2 };
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
// @bun
|
||||
import {
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/HelpBar.tsx
|
||||
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
||||
@@ -28,9 +28,9 @@ var KEY_DISPLAY = {
|
||||
};
|
||||
var SEPARATOR = " ";
|
||||
var ELLIPSIS = "\u2026";
|
||||
function HelpBar(props = {}) {
|
||||
const accent = () => props.accentColor ?? theme.primary;
|
||||
const muted = () => props.mutedColor ?? theme.muted;
|
||||
function HelpBar2(props = {}) {
|
||||
const accent = () => props.accentColor ?? theme2.primary;
|
||||
const muted = () => props.mutedColor ?? theme2.muted;
|
||||
const renderer = useRenderer();
|
||||
const [width, setWidth] = createSignal(0);
|
||||
let container;
|
||||
@@ -118,4 +118,4 @@ function HelpBar(props = {}) {
|
||||
})();
|
||||
}
|
||||
|
||||
export { HelpBar };
|
||||
export { HelpBar2 };
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
// @bun
|
||||
import {
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/KeyValue.tsx
|
||||
import { memo as _$memo } from "@opentui/solid";
|
||||
import { effect as _$effect } from "@opentui/solid";
|
||||
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||
import { createComponent as _$createComponent } 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 { Show } from "solid-js";
|
||||
function KeyValue2(props) {
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box"), _el$2 = _$createElement("text");
|
||||
_$insertNode(_el$, _el$2);
|
||||
_$setProp(_el$, "flexShrink", 0);
|
||||
_$setProp(_el$, "flexDirection", "row");
|
||||
_$setProp(_el$, "justifyContent", "space-between");
|
||||
_$insert(_el$2, () => props.label);
|
||||
_$insert(_el$, _$createComponent(Show, {
|
||||
get when() {
|
||||
return props.children;
|
||||
},
|
||||
get fallback() {
|
||||
return (() => {
|
||||
var _el$3 = _$createElement("text");
|
||||
_$insert(_el$3, () => props.value ?? "");
|
||||
_$effect((_$p) => _$setProp(_el$3, "fg", props.valueColor, _$p));
|
||||
return _el$3;
|
||||
})();
|
||||
},
|
||||
get children() {
|
||||
return props.children;
|
||||
}
|
||||
}), null);
|
||||
_$effect((_$p) => _$setProp(_el$2, "fg", theme2.muted, _$p));
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
export { KeyValue2 };
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
// @bun
|
||||
// src/context/help.ts
|
||||
import { createSignal } from "solid-js";
|
||||
var [helpOpen, setHelpOpen] = createSignal(false);
|
||||
function toggleHelp() {
|
||||
setHelpOpen((open) => !open);
|
||||
}
|
||||
|
||||
export { helpOpen, toggleHelp };
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// @bun
|
||||
// src/context/help.ts
|
||||
import { createSignal } from "solid-js";
|
||||
var [helpOpen2, setHelpOpen] = createSignal(false);
|
||||
function toggleHelp2() {
|
||||
setHelpOpen((open) => !open);
|
||||
}
|
||||
|
||||
export { helpOpen2, toggleHelp2 };
|
||||
+18
-11
@@ -1,11 +1,11 @@
|
||||
// @bun
|
||||
import {
|
||||
Modal
|
||||
} from "./chunk-zjfxbvmq.js";
|
||||
helpOpen2,
|
||||
toggleHelp2
|
||||
} from "./chunk-ee2qaswx.js";
|
||||
import {
|
||||
helpOpen,
|
||||
toggleHelp
|
||||
} from "./chunk-cyvpvpws.js";
|
||||
Modal2
|
||||
} from "./chunk-t2cszvff.js";
|
||||
|
||||
// src/components/HelpModal.tsx
|
||||
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||
@@ -18,6 +18,7 @@ import { createElement as _$createElement } from "@opentui/solid";
|
||||
import { TextAttributes } from "@opentui/core";
|
||||
import { formatCommandBindings } from "@opentui/keymap/extras";
|
||||
import { useKeymapSelector } from "@opentui/keymap/solid";
|
||||
import { createSignal } from "solid-js";
|
||||
var KEY_DISPLAY = {
|
||||
up: "\u2191",
|
||||
down: "\u2193",
|
||||
@@ -26,9 +27,11 @@ var KEY_DISPLAY = {
|
||||
enter: "Enter",
|
||||
escape: "Esc"
|
||||
};
|
||||
function HelpModal(props = {}) {
|
||||
function HelpModal2(props = {}) {
|
||||
const [scope, setScope] = createSignal(null);
|
||||
const entries = useKeymapSelector((keymap) => keymap.getCommandEntries({
|
||||
visibility: "active"
|
||||
visibility: "active",
|
||||
focused: scope()
|
||||
}).map((entry) => ({
|
||||
command: entry.command.name,
|
||||
keys: formatCommandBindings(entry.bindings, {
|
||||
@@ -36,11 +39,15 @@ function HelpModal(props = {}) {
|
||||
}) ?? "",
|
||||
label: typeof entry.command.desc === "string" ? entry.command.desc : entry.command.name
|
||||
})));
|
||||
return _$createComponent(Modal, {
|
||||
return _$createComponent(Modal2, {
|
||||
get open() {
|
||||
return helpOpen();
|
||||
return helpOpen2();
|
||||
},
|
||||
onDismiss: toggleHelp2,
|
||||
onOpen: (previous) => setScope(() => previous),
|
||||
get dismissKey() {
|
||||
return props.dismissKey;
|
||||
},
|
||||
onDismiss: toggleHelp,
|
||||
get accentColor() {
|
||||
return props.accentColor;
|
||||
},
|
||||
@@ -77,4 +84,4 @@ function HelpModal(props = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
export { HelpModal };
|
||||
export { HelpModal2 };
|
||||
Vendored
-60
@@ -1,60 +0,0 @@
|
||||
// @bun
|
||||
// src/config.ts
|
||||
var COLOR_KEYS = [
|
||||
"base00",
|
||||
"base01",
|
||||
"base02",
|
||||
"base03",
|
||||
"base04",
|
||||
"base05",
|
||||
"base06",
|
||||
"base07",
|
||||
"base08",
|
||||
"base09",
|
||||
"base0a",
|
||||
"base0b",
|
||||
"base0c",
|
||||
"base0d",
|
||||
"base0e",
|
||||
"base0f"
|
||||
];
|
||||
function mergeColors(base, user) {
|
||||
const merged = {};
|
||||
for (const key of COLOR_KEYS) {
|
||||
merged[key] = user[key] || base[key] || "";
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
function mergeConfig(base, user) {
|
||||
return {
|
||||
nerdFonts: user.nerd_fonts ?? base.nerd_fonts ?? false,
|
||||
emojiFonts: user.emoji_fonts ?? base.emoji_fonts ?? false,
|
||||
reducedMotion: user.reduced_motion ?? base.reduced_motion ?? false,
|
||||
mouse: user.mouse ?? base.mouse ?? true,
|
||||
border: user.border || base.border || "rounded",
|
||||
colors: mergeColors(base.colors ?? {}, user.colors ?? {})
|
||||
};
|
||||
}
|
||||
function normalizeColor(hex) {
|
||||
const trimmed = hex.trim();
|
||||
if (trimmed && !trimmed.startsWith("#"))
|
||||
return `#${trimmed}`;
|
||||
return trimmed;
|
||||
}
|
||||
function normalizeColors(colors) {
|
||||
const out = {};
|
||||
for (const key of COLOR_KEYS)
|
||||
out[key] = normalizeColor(colors[key]);
|
||||
return out;
|
||||
}
|
||||
var OPENTUI_BORDER_STYLES = [
|
||||
"single",
|
||||
"double",
|
||||
"rounded",
|
||||
"heavy"
|
||||
];
|
||||
function resolveBorderStyle(name) {
|
||||
return OPENTUI_BORDER_STYLES.includes(name) ? name : "rounded";
|
||||
}
|
||||
|
||||
export { COLOR_KEYS, mergeColors, mergeConfig, normalizeColor, normalizeColors, resolveBorderStyle };
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
// src/context/helper.tsx
|
||||
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||
import { createContext, useContext } from "solid-js";
|
||||
function createSimpleContext(input) {
|
||||
function createSimpleContext2(input) {
|
||||
const ctx = createContext();
|
||||
return {
|
||||
provider: (props) => {
|
||||
@@ -23,4 +23,4 @@ function createSimpleContext(input) {
|
||||
};
|
||||
}
|
||||
|
||||
export { createSimpleContext };
|
||||
export { createSimpleContext2 };
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
// @bun
|
||||
import {
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/Spinner.tsx
|
||||
import { setProp as _$setProp } from "@opentui/solid";
|
||||
import { effect as _$effect } from "@opentui/solid";
|
||||
import { createElement as _$createElement } from "@opentui/solid";
|
||||
import"opentui-spinner/solid";
|
||||
function Spinner2(props) {
|
||||
return (() => {
|
||||
var _el$ = _$createElement("spinner");
|
||||
_$effect((_p$) => {
|
||||
var { name: _v$, frames: _v$2, interval: _v$3 } = props, _v$4 = props.autoplay ?? !theme2.reducedMotion, _v$5 = props.color ?? theme2.primary, _v$6 = props.backgroundColor;
|
||||
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "name", _v$, _p$.e));
|
||||
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "frames", _v$2, _p$.t));
|
||||
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "interval", _v$3, _p$.a));
|
||||
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$, "autoplay", _v$4, _p$.o));
|
||||
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$, "color", _v$5, _p$.i));
|
||||
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$, "backgroundColor", _v$6, _p$.n));
|
||||
return _p$;
|
||||
}, {
|
||||
e: undefined,
|
||||
t: undefined,
|
||||
a: undefined,
|
||||
o: undefined,
|
||||
i: undefined,
|
||||
n: undefined
|
||||
});
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
export { Spinner2 };
|
||||
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
// @bun
|
||||
// src/config.ts
|
||||
import { z } from "zod";
|
||||
var COLOR_KEYS2 = [
|
||||
"base00",
|
||||
"base01",
|
||||
"base02",
|
||||
"base03",
|
||||
"base04",
|
||||
"base05",
|
||||
"base06",
|
||||
"base07",
|
||||
"base08",
|
||||
"base09",
|
||||
"base0a",
|
||||
"base0b",
|
||||
"base0c",
|
||||
"base0d",
|
||||
"base0e",
|
||||
"base0f"
|
||||
];
|
||||
var OPENTUI_BORDER_STYLES2 = ["single", "double", "rounded", "heavy"];
|
||||
var hexColor = z.string().regex(/^#?[0-9a-fA-F]{6}$/, 'expected a hex color like "#1e1e2e"');
|
||||
var colorsShape = {};
|
||||
for (const key of COLOR_KEYS2)
|
||||
colorsShape[key] = hexColor.optional();
|
||||
var ThemeConfigSchema2 = z.object({
|
||||
nerd_fonts: z.boolean().optional(),
|
||||
emoji_fonts: z.boolean().optional(),
|
||||
reduced_motion: z.boolean().optional(),
|
||||
mouse: z.boolean().optional(),
|
||||
border: z.enum(OPENTUI_BORDER_STYLES2).optional(),
|
||||
colors: z.object(colorsShape).optional()
|
||||
});
|
||||
function layerConfigs2(layers) {
|
||||
const out = {};
|
||||
for (const layer of layers) {
|
||||
const { colors, ...rest } = layer;
|
||||
Object.assign(out, rest);
|
||||
if (colors)
|
||||
out.colors = { ...out.colors, ...colors };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function resolveConfig2(config) {
|
||||
const colors = {};
|
||||
for (const key of COLOR_KEYS2)
|
||||
colors[key] = normalizeColor2(config.colors?.[key] ?? "");
|
||||
return {
|
||||
nerdFonts: config.nerd_fonts ?? false,
|
||||
emojiFonts: config.emoji_fonts ?? false,
|
||||
reducedMotion: config.reduced_motion ?? false,
|
||||
mouse: config.mouse ?? true,
|
||||
border: config.border ?? "rounded",
|
||||
colors
|
||||
};
|
||||
}
|
||||
function normalizeColor2(hex) {
|
||||
const trimmed = hex.trim();
|
||||
if (trimmed && !trimmed.startsWith("#"))
|
||||
return `#${trimmed}`;
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export { COLOR_KEYS2, OPENTUI_BORDER_STYLES2, ThemeConfigSchema2, layerConfigs2, resolveConfig2, normalizeColor2 };
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
// @bun
|
||||
import {
|
||||
ThemeConfigSchema2,
|
||||
layerConfigs2,
|
||||
resolveConfig2
|
||||
} from "./chunk-njvj322f.js";
|
||||
import {
|
||||
notify2
|
||||
} from "./chunk-6ggk5fg7.js";
|
||||
import {
|
||||
buildPresets2
|
||||
} from "./chunk-yyg967q8.js";
|
||||
import {
|
||||
configPath2
|
||||
} from "./chunk-xykm10y9.js";
|
||||
|
||||
// src/index.ts
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { parse as parseYAML } from "yaml";
|
||||
import { z } from "zod";
|
||||
function reportThemeError(path, message) {
|
||||
const text = `${path}: ${message}`;
|
||||
console.error(`[ilovetui theme] ${text}`);
|
||||
notify2(text, { kind: "warning", duration: 0 });
|
||||
}
|
||||
function readThemeFile(path) {
|
||||
if (!existsSync(path))
|
||||
return {};
|
||||
let raw;
|
||||
try {
|
||||
raw = parseYAML(readFileSync(path, "utf8"));
|
||||
} catch (error) {
|
||||
reportThemeError(path, error instanceof Error ? error.message : String(error));
|
||||
return {};
|
||||
}
|
||||
const result = ThemeConfigSchema2.safeParse(raw ?? {});
|
||||
if (!result.success) {
|
||||
reportThemeError(path, z.prettifyError(result.error));
|
||||
return {};
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
var fileLayers = [readThemeFile(join(import.meta.dir, "default.yaml")), readThemeFile(configPath2())];
|
||||
function buildTheme(overrides) {
|
||||
const resolved = resolveConfig2(layerConfigs2([...fileLayers, overrides]));
|
||||
const colors = resolved.colors;
|
||||
return {
|
||||
...colors,
|
||||
background: colors.base00,
|
||||
subtleBg: colors.base01,
|
||||
selection: colors.base02,
|
||||
subtle: colors.base03,
|
||||
muted: colors.base04,
|
||||
text: colors.base05,
|
||||
primary: colors.base0d,
|
||||
success: colors.base0b,
|
||||
warning: colors.base09,
|
||||
error: colors.base08,
|
||||
nerdFonts: resolved.nerdFonts,
|
||||
emojiFonts: resolved.emojiFonts,
|
||||
reducedMotion: resolved.reducedMotion,
|
||||
mouse: resolved.mouse,
|
||||
borderStyle: resolved.border
|
||||
};
|
||||
}
|
||||
var theme2 = buildTheme({});
|
||||
var presets2 = buildPresets2(theme2);
|
||||
function configureTheme2(overrides = {}) {
|
||||
Object.assign(theme2, buildTheme(overrides));
|
||||
const next = buildPresets2(theme2);
|
||||
for (const key of Object.keys(next))
|
||||
Object.assign(presets2[key], next[key]);
|
||||
}
|
||||
function icon2(codepoint) {
|
||||
return theme2.nerdFonts ? `${codepoint} ` : "";
|
||||
}
|
||||
|
||||
export { theme2, presets2, configureTheme2, icon2 };
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
// @bun
|
||||
import {
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/MinSizeGuard.tsx
|
||||
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||
@@ -13,7 +13,7 @@ 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) {
|
||||
function MinSizeGuard2(props) {
|
||||
const dimensions = useTerminalDimensions();
|
||||
const minWidth = () => props.minWidth ?? 80;
|
||||
const minHeight = () => props.minHeight ?? 24;
|
||||
@@ -34,7 +34,7 @@ function MinSizeGuard(props) {
|
||||
_$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;
|
||||
var _v$ = dimensions().width, _v$2 = dimensions().height, _v$3 = props.backgroundColor ?? theme2.background, _v$4 = props.textColor ?? theme2.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));
|
||||
@@ -51,4 +51,4 @@ Minimum size: ${minWidth()}x${minHeight()} \u2014 current: ${dimensions().width}
|
||||
})];
|
||||
}
|
||||
|
||||
export { MinSizeGuard };
|
||||
export { MinSizeGuard2 };
|
||||
+11
-11
@@ -1,10 +1,10 @@
|
||||
// @bun
|
||||
import {
|
||||
Badge
|
||||
} from "./chunk-bscbaa87.js";
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
import {
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
Badge2
|
||||
} from "./chunk-v9cetqzr.js";
|
||||
|
||||
// src/components/Toggle.tsx
|
||||
import { setProp as _$setProp } from "@opentui/solid";
|
||||
@@ -15,10 +15,10 @@ import { memo as _$memo } from "@opentui/solid";
|
||||
import { createElement as _$createElement } from "@opentui/solid";
|
||||
import { TextAttributes } from "@opentui/core";
|
||||
import { Show } from "solid-js";
|
||||
function Toggle(props) {
|
||||
const onColor = () => props.onColor ?? theme.success;
|
||||
const offColor = () => props.offColor ?? theme.muted;
|
||||
const nerdFonts = () => props.withNerdfont ?? theme.nerdFonts;
|
||||
function Toggle2(props) {
|
||||
const onColor = () => props.onColor ?? theme2.success;
|
||||
const offColor = () => props.offColor ?? theme2.muted;
|
||||
const nerdFonts = () => props.withNerdfont ?? theme2.nerdFonts;
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box");
|
||||
_$insert(_el$, _$createComponent(Show, {
|
||||
@@ -42,7 +42,7 @@ function Toggle(props) {
|
||||
})();
|
||||
},
|
||||
get children() {
|
||||
return _$createComponent(Badge, {
|
||||
return _$createComponent(Badge2, {
|
||||
get label() {
|
||||
return props.on ? " \u25CF" : "\u25CF ";
|
||||
},
|
||||
@@ -53,9 +53,9 @@ function Toggle(props) {
|
||||
});
|
||||
}
|
||||
}));
|
||||
_$effect((_$p) => _$setProp(_el$, "onMouseDown", theme.mouse ? props.onToggle : undefined, _$p));
|
||||
_$effect((_$p) => _$setProp(_el$, "onMouseDown", theme2.mouse ? props.onToggle : undefined, _$p));
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
export { Toggle };
|
||||
export { Toggle2 };
|
||||
+12
-7
@@ -1,8 +1,8 @@
|
||||
// @bun
|
||||
import {
|
||||
presets,
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
theme2,
|
||||
presets2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/Sidebar.tsx
|
||||
import { use as _$use } from "@opentui/solid";
|
||||
@@ -14,7 +14,7 @@ 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";
|
||||
import { createEffect, Show } from "solid-js";
|
||||
function truncateWithEllipsis(text, maxWidth) {
|
||||
if (maxWidth <= 0)
|
||||
return "";
|
||||
@@ -35,7 +35,7 @@ function itemIndexAtScreenY(el, screenY) {
|
||||
var BORDER_WIDTH = 2;
|
||||
var ITEM_LEFT_PADDING = 1;
|
||||
var ITEM_INDICATOR_WIDTH = 2;
|
||||
function Sidebar(props) {
|
||||
function Sidebar2(props) {
|
||||
let select;
|
||||
const contentWidth = () => Math.max(0, (props.width ?? 24) - BORDER_WIDTH);
|
||||
const itemTextWidth = () => Math.max(0, contentWidth() - ITEM_LEFT_PADDING - ITEM_INDICATOR_WIDTH);
|
||||
@@ -65,6 +65,11 @@ function Sidebar(props) {
|
||||
else if (event.scroll.direction === "up")
|
||||
select.moveUp(steps);
|
||||
};
|
||||
createEffect(() => {
|
||||
const index = props.items.findIndex((item) => item.value === props.value);
|
||||
if (select && index !== -1 && select.getSelectedIndex() !== index)
|
||||
select.setSelectedIndex(index);
|
||||
});
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box"), _el$5 = _$createElement("select");
|
||||
_$insertNode(_el$, _el$5);
|
||||
@@ -109,7 +114,7 @@ function Sidebar(props) {
|
||||
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 = displayItems(), _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;
|
||||
var _v$ = props.width ?? 24, _v$2 = theme2.borderStyle, _v$3 = props.focused ? props.accentColor ?? theme2.primary : props.mutedColor ?? theme2.muted, _v$4 = props.focused, _v$5 = displayItems(), _v$6 = theme2.mouse ? handleMouseDown : undefined, _v$7 = theme2.mouse ? handleMouseScroll : undefined, _v$8 = props.backgroundColor ?? "transparent", _v$9 = props.textColor ?? presets2.select.textColor, _v$0 = props.focusedBackgroundColor ?? "transparent", _v$1 = props.focusedTextColor ?? presets2.select.focusedTextColor, _v$10 = props.selectedBackgroundColor ?? presets2.select.selectedBackgroundColor, _v$11 = props.selectedTextColor ?? presets2.select.selectedTextColor, _v$12 = props.descriptionColor ?? presets2.select.descriptionColor, _v$13 = props.selectedDescriptionColor ?? presets2.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));
|
||||
@@ -147,4 +152,4 @@ function Sidebar(props) {
|
||||
})();
|
||||
}
|
||||
|
||||
export { Sidebar };
|
||||
export { Sidebar2 };
|
||||
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
// @bun
|
||||
import {
|
||||
theme2,
|
||||
presets2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/ListRow.tsx
|
||||
import { effect as _$effect } from "@opentui/solid";
|
||||
import { createComponent as _$createComponent } 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 { TextAttributes } from "@opentui/core";
|
||||
import { Show } from "solid-js";
|
||||
function ListRow2(props) {
|
||||
const tone = (fallback) => props.selected ? presets2.select.selectedTextColor : fallback;
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("box"), _el$4 = _$createElement("text");
|
||||
_$insertNode(_el$, _el$2);
|
||||
_$setProp(_el$, "flexShrink", 0);
|
||||
_$setProp(_el$, "flexDirection", "column");
|
||||
_$setProp(_el$, "paddingLeft", 1);
|
||||
_$setProp(_el$, "paddingRight", 1);
|
||||
_$insertNode(_el$2, _el$3);
|
||||
_$setProp(_el$2, "flexDirection", "row");
|
||||
_$setProp(_el$2, "justifyContent", "space-between");
|
||||
_$setProp(_el$2, "alignItems", "center");
|
||||
_$setProp(_el$2, "height", 1);
|
||||
_$insertNode(_el$3, _el$4);
|
||||
_$setProp(_el$3, "flexGrow", 1);
|
||||
_$setProp(_el$3, "flexShrink", 1);
|
||||
_$setProp(_el$3, "minWidth", 0);
|
||||
_$setProp(_el$3, "marginRight", 1);
|
||||
_$setProp(_el$3, "overflow", "hidden");
|
||||
_$setProp(_el$4, "wrapMode", "none");
|
||||
_$insert(_el$4, () => props.label);
|
||||
_$insert(_el$2, _$createComponent(Show, {
|
||||
get when() {
|
||||
return props.right;
|
||||
},
|
||||
get children() {
|
||||
var _el$5 = _$createElement("box");
|
||||
_$setProp(_el$5, "flexShrink", 0);
|
||||
_$setProp(_el$5, "flexDirection", "row");
|
||||
_$setProp(_el$5, "alignItems", "center");
|
||||
_$insert(_el$5, () => props.right?.(tone));
|
||||
return _el$5;
|
||||
}
|
||||
}), null);
|
||||
_$insert(_el$, () => props.below?.(tone), null);
|
||||
_$effect((_p$) => {
|
||||
var _v$ = props.id, _v$2 = props.selected ? presets2.select.selectedBackgroundColor : "transparent", _v$3 = theme2.mouse && props.onClick ? () => props.onClick?.() : undefined, _v$4 = tone(), _v$5 = props.bold ? TextAttributes.BOLD : undefined;
|
||||
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "id", _v$, _p$.e));
|
||||
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "backgroundColor", _v$2, _p$.t));
|
||||
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "onMouseDown", _v$3, _p$.a));
|
||||
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$4, "fg", _v$4, _p$.o));
|
||||
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$4, "attributes", _v$5, _p$.i));
|
||||
return _p$;
|
||||
}, {
|
||||
e: undefined,
|
||||
t: undefined,
|
||||
a: undefined,
|
||||
o: undefined,
|
||||
i: undefined
|
||||
});
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
export { ListRow2 };
|
||||
Vendored
+140
@@ -0,0 +1,140 @@
|
||||
// @bun
|
||||
import {
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/Modal.tsx
|
||||
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||
import { effect as _$effect } from "@opentui/solid";
|
||||
import { insert as _$insert } from "@opentui/solid";
|
||||
import { use as _$use } from "@opentui/solid";
|
||||
import { setProp as _$setProp } from "@opentui/solid";
|
||||
import { createElement as _$createElement } from "@opentui/solid";
|
||||
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||
import { RGBA } from "@opentui/core";
|
||||
import { useBindings } from "@opentui/keymap/solid";
|
||||
import { Portal, useRenderer, useTerminalDimensions } from "@opentui/solid";
|
||||
import { createSignal, onCleanup, onMount, Show } from "solid-js";
|
||||
function Modal2(props) {
|
||||
return _$createComponent(Portal, {
|
||||
get children() {
|
||||
return _$createComponent(Show, {
|
||||
get when() {
|
||||
return props.open;
|
||||
},
|
||||
get children() {
|
||||
return _$createComponent(ModalPanel, props);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function ModalPanel(props) {
|
||||
const renderer = useRenderer();
|
||||
const dimensions = useTerminalDimensions();
|
||||
const [panel, setPanel] = createSignal();
|
||||
const screenCap = () => Math.max(5, dimensions().height - 4);
|
||||
const maxPanelHeight = () => Math.min(props.maxHeight ?? screenCap(), screenCap());
|
||||
const maxContentHeight = () => Math.max(1, maxPanelHeight() - 4);
|
||||
const maxPanelWidth = () => Math.max(10, dimensions().width - 4);
|
||||
const panelWidth = () => Math.min(props.width ?? 50, maxPanelWidth());
|
||||
let previousFocus = null;
|
||||
onMount(() => {
|
||||
previousFocus = renderer.currentFocusedRenderable;
|
||||
props.onOpen?.(previousFocus);
|
||||
panel()?.focus();
|
||||
});
|
||||
onCleanup(() => {
|
||||
if (previousFocus && !previousFocus.isDestroyed)
|
||||
previousFocus.focus();
|
||||
});
|
||||
const dismissKey = props.dismissKey ?? "escape";
|
||||
if (dismissKey !== false || props.commands || props.bindings) {
|
||||
useBindings(() => ({
|
||||
target: panel,
|
||||
commands: [...dismissKey !== false ? [{
|
||||
name: "modal_dismiss",
|
||||
desc: "close",
|
||||
shortHelp: true,
|
||||
run: () => props.onDismiss?.()
|
||||
}] : [], ...props.commands ?? []],
|
||||
bindings: [...dismissKey !== false ? [{
|
||||
key: dismissKey,
|
||||
cmd: "modal_dismiss"
|
||||
}] : [], ...props.bindings ?? []]
|
||||
}));
|
||||
}
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box"), _el$2 = _$createElement("box");
|
||||
_$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", 2000);
|
||||
_$use(setPanel, _el$2);
|
||||
_$setProp(_el$2, "focusable", true);
|
||||
_$setProp(_el$2, "border", true);
|
||||
_$setProp(_el$2, "flexDirection", "column");
|
||||
_$setProp(_el$2, "paddingLeft", 2);
|
||||
_$setProp(_el$2, "paddingRight", 2);
|
||||
_$setProp(_el$2, "paddingTop", 1);
|
||||
_$setProp(_el$2, "paddingBottom", 1);
|
||||
_$insert(_el$2, _$createComponent(Show, {
|
||||
get when() {
|
||||
return props.scrollable;
|
||||
},
|
||||
get fallback() {
|
||||
return (() => {
|
||||
var _el$4 = _$createElement("box");
|
||||
_$setProp(_el$4, "flexDirection", "column");
|
||||
_$setProp(_el$4, "overflow", "hidden");
|
||||
_$insert(_el$4, () => props.children);
|
||||
_$effect((_$p) => _$setProp(_el$4, "maxHeight", maxContentHeight(), _$p));
|
||||
return _el$4;
|
||||
})();
|
||||
},
|
||||
get children() {
|
||||
var _el$3 = _$createElement("scrollbox");
|
||||
_$setProp(_el$3, "flexShrink", 1);
|
||||
_$setProp(_el$3, "scrollY", true);
|
||||
_$setProp(_el$3, "scrollX", false);
|
||||
_$setProp(_el$3, "focusable", false);
|
||||
_$insert(_el$3, () => props.children);
|
||||
_$effect((_$p) => _$setProp(_el$3, "maxHeight", maxContentHeight(), _$p));
|
||||
return _el$3;
|
||||
}
|
||||
}));
|
||||
_$effect((_p$) => {
|
||||
var _v$ = dimensions().width, _v$2 = dimensions().height, _v$3 = props.backdropColor ?? RGBA.fromInts(0, 0, 0, 150), _v$4 = theme2.mouse ? props.onDismiss : undefined, _v$5 = panelWidth(), _v$6 = maxPanelHeight(), _v$7 = theme2.borderStyle, _v$8 = props.accentColor ?? theme2.primary, _v$9 = props.accentColor ?? theme2.primary, _v$0 = props.backgroundColor ?? theme2.background, _v$1 = theme2.mouse ? (event) => event.stopPropagation() : undefined;
|
||||
_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$, "onMouseDown", _v$4, _p$.o));
|
||||
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$2, "width", _v$5, _p$.i));
|
||||
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$2, "maxHeight", _v$6, _p$.n));
|
||||
_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$9 !== _p$.r && (_p$.r = _$setProp(_el$2, "focusedBorderColor", _v$9, _p$.r));
|
||||
_v$0 !== _p$.d && (_p$.d = _$setProp(_el$2, "backgroundColor", _v$0, _p$.d));
|
||||
_v$1 !== _p$.l && (_p$.l = _$setProp(_el$2, "onMouseDown", _v$1, _p$.l));
|
||||
return _p$;
|
||||
}, {
|
||||
e: undefined,
|
||||
t: undefined,
|
||||
a: undefined,
|
||||
o: undefined,
|
||||
i: undefined,
|
||||
n: undefined,
|
||||
s: undefined,
|
||||
h: undefined,
|
||||
r: undefined,
|
||||
d: undefined,
|
||||
l: undefined
|
||||
});
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
export { Modal2 };
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
// @bun
|
||||
import {
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/SignalBars.tsx
|
||||
import { effect as _$effect } from "@opentui/solid";
|
||||
@@ -11,7 +11,7 @@ import { setProp as _$setProp } from "@opentui/solid";
|
||||
import { createElement as _$createElement } from "@opentui/solid";
|
||||
import { For } from "solid-js";
|
||||
var BARS = ["\u2581", "\u2583", "\u2585", "\u2587"];
|
||||
function SignalBars(props) {
|
||||
function SignalBars2(props) {
|
||||
const level = () => Math.floor(props.signal * BARS.length / 100);
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box");
|
||||
@@ -21,7 +21,7 @@ function SignalBars(props) {
|
||||
children: (bar, i) => (() => {
|
||||
var _el$2 = _$createElement("text");
|
||||
_$insert(_el$2, bar);
|
||||
_$effect((_$p) => _$setProp(_el$2, "fg", i() < level() ? props.filledColor : props.emptyColor ?? theme.muted, _$p));
|
||||
_$effect((_$p) => _$setProp(_el$2, "fg", i() < level() ? props.filledColor : props.emptyColor ?? theme2.muted, _$p));
|
||||
return _el$2;
|
||||
})()
|
||||
}));
|
||||
@@ -29,4 +29,4 @@ function SignalBars(props) {
|
||||
})();
|
||||
}
|
||||
|
||||
export { SignalBars };
|
||||
export { SignalBars2 };
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
// @bun
|
||||
import {
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/Badge.tsx
|
||||
import { insert as _$insert } from "@opentui/solid";
|
||||
@@ -14,11 +14,11 @@ 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 Badge(props) {
|
||||
const textColor = () => props.textColor ?? theme.background;
|
||||
const backgroundColor = () => props.backgroundColor ?? theme.background;
|
||||
function Badge2(props) {
|
||||
const textColor = () => props.textColor ?? theme2.background;
|
||||
const backgroundColor = () => props.backgroundColor ?? theme2.background;
|
||||
const attributes = () => props.bold === false ? undefined : TextAttributes.BOLD;
|
||||
const nerdFonts = () => props.withNerdfont ?? theme.nerdFonts;
|
||||
const nerdFonts = () => props.withNerdfont ?? theme2.nerdFonts;
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box"), _el$4 = _$createElement("text");
|
||||
_$insertNode(_el$, _el$4);
|
||||
@@ -80,4 +80,4 @@ function Badge(props) {
|
||||
})();
|
||||
}
|
||||
|
||||
export { Badge };
|
||||
export { Badge2 };
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
// @bun
|
||||
import {
|
||||
useFocusWithin2
|
||||
} from "./chunk-59dzyjgd.js";
|
||||
|
||||
// src/components/Pane.tsx
|
||||
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||
import { insert as _$insert } from "@opentui/solid";
|
||||
import { use as _$use } from "@opentui/solid";
|
||||
import { spread as _$spread } from "@opentui/solid";
|
||||
import { createElement as _$createElement } from "@opentui/solid";
|
||||
import { createContext, createSignal, splitProps, useContext } from "solid-js";
|
||||
var PaneContext = createContext();
|
||||
function Pane2(props) {
|
||||
const [local, rest] = splitProps(props, ["handle", "children"]);
|
||||
const [el, setEl] = createSignal();
|
||||
const focused = useFocusWithin2(el);
|
||||
const handle = {
|
||||
target: el,
|
||||
focused,
|
||||
focus: () => el()?.focus()
|
||||
};
|
||||
local.handle?.(handle);
|
||||
return _$createComponent(PaneContext.Provider, {
|
||||
value: handle,
|
||||
get children() {
|
||||
var _el$ = _$createElement("box");
|
||||
_$use(setEl, _el$);
|
||||
_$spread(_el$, rest, true);
|
||||
_$insert(_el$, () => local.children);
|
||||
return _el$;
|
||||
}
|
||||
});
|
||||
}
|
||||
function usePane2() {
|
||||
const pane = useContext(PaneContext);
|
||||
if (!pane)
|
||||
throw new Error("usePane() must be used inside a <Pane>");
|
||||
return pane;
|
||||
}
|
||||
|
||||
export { Pane2, usePane2 };
|
||||
+9
-9
@@ -1,7 +1,7 @@
|
||||
// @bun
|
||||
import {
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/Tabs.tsx
|
||||
import { effect as _$effect } from "@opentui/solid";
|
||||
@@ -14,7 +14,7 @@ import { createElement as _$createElement } from "@opentui/solid";
|
||||
import { BorderChars, CliRenderEvents, TextAttributes } from "@opentui/core";
|
||||
import { onResize, useRenderer } from "@opentui/solid";
|
||||
import { createMemo, createSignal, Index } from "solid-js";
|
||||
function stepTabValue(items, value, delta) {
|
||||
function stepTabValue2(items, value, delta) {
|
||||
const index = items.findIndex((t) => t.value === value);
|
||||
if (index === -1)
|
||||
return value;
|
||||
@@ -58,7 +58,7 @@ function fitCount(items, start, available) {
|
||||
}
|
||||
return Math.max(count, 1);
|
||||
}
|
||||
function Tabs(props) {
|
||||
function Tabs2(props) {
|
||||
const renderer = useRenderer();
|
||||
const [width, setWidth] = createSignal(0);
|
||||
let container;
|
||||
@@ -98,10 +98,10 @@ function Tabs(props) {
|
||||
}
|
||||
return shown;
|
||||
});
|
||||
const accent = () => props.accentColor ?? theme.primary;
|
||||
const muted = () => props.mutedColor ?? theme.muted;
|
||||
const accent = () => props.accentColor ?? theme2.primary;
|
||||
const muted = () => props.mutedColor ?? theme2.muted;
|
||||
const borderColor = () => props.focused ? accent() : muted();
|
||||
const chars = BorderChars[theme.borderStyle];
|
||||
const chars = BorderChars[theme2.borderStyle];
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("box"), _el$4 = _$createElement("text");
|
||||
_$insertNode(_el$, _el$2);
|
||||
@@ -131,7 +131,7 @@ function Tabs(props) {
|
||||
_$setProp(_el$6, "selectable", false);
|
||||
_$insert(_el$6, () => ` ${s().label} `);
|
||||
_$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 = theme2.mouse && s().value !== undefined ? () => props.onChange(s().value) : undefined;
|
||||
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$6, "fg", _v$3, _p$.e));
|
||||
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$6, "attributes", _v$4, _p$.t));
|
||||
_v$5 !== _p$.a && (_p$.a = _$setProp(_el$6, "onMouseDown", _v$5, _p$.a));
|
||||
@@ -165,4 +165,4 @@ function Tabs(props) {
|
||||
})();
|
||||
}
|
||||
|
||||
export { stepTabValue, Tabs };
|
||||
export { stepTabValue2, Tabs2 };
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
// @bun
|
||||
import {
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
import {
|
||||
Box2
|
||||
} from "./chunk-8r4rajaz.js";
|
||||
import {
|
||||
Tabs2
|
||||
} from "./chunk-vf2ybhtx.js";
|
||||
|
||||
// src/components/TabbedPanel.tsx
|
||||
import { insert as _$insert } from "@opentui/solid";
|
||||
import { memo as _$memo } from "@opentui/solid";
|
||||
import { createComponent as _$createComponent } from "@opentui/solid";
|
||||
import { setProp as _$setProp } from "@opentui/solid";
|
||||
import { createElement as _$createElement } from "@opentui/solid";
|
||||
function TabbedPanel2(props) {
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box");
|
||||
_$setProp(_el$, "flexDirection", "column");
|
||||
_$setProp(_el$, "flexGrow", 1);
|
||||
_$insert(_el$, _$createComponent(Tabs2, {
|
||||
get items() {
|
||||
return props.items;
|
||||
},
|
||||
get value() {
|
||||
return props.value;
|
||||
},
|
||||
get onChange() {
|
||||
return props.onChange;
|
||||
},
|
||||
get focused() {
|
||||
return props.focused;
|
||||
}
|
||||
}), null);
|
||||
_$insert(_el$, _$createComponent(Box2, {
|
||||
flexGrow: 1,
|
||||
border: ["left", "right", "bottom"],
|
||||
get borderColor() {
|
||||
return _$memo(() => !!props.focused)() ? theme2.primary : theme2.muted;
|
||||
},
|
||||
flexDirection: "column",
|
||||
get children() {
|
||||
return props.children;
|
||||
}
|
||||
}), null);
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
export { TabbedPanel2 };
|
||||
Vendored
-45
@@ -1,45 +0,0 @@
|
||||
// @bun
|
||||
import {
|
||||
configPath,
|
||||
readYamlFile
|
||||
} from "./chunk-7f8jagy5.js";
|
||||
import {
|
||||
mergeConfig,
|
||||
normalizeColors,
|
||||
resolveBorderStyle
|
||||
} from "./chunk-hcq62p48.js";
|
||||
import {
|
||||
buildPresets
|
||||
} from "./chunk-cnhhssmz.js";
|
||||
|
||||
// src/index.ts
|
||||
import { join } from "path";
|
||||
function buildTheme() {
|
||||
const defaultConfigPath = join(import.meta.dir, "default.yaml");
|
||||
const base = readYamlFile(defaultConfigPath) ?? {};
|
||||
const user = readYamlFile(configPath()) ?? {};
|
||||
const merged = mergeConfig(base, user);
|
||||
const colors = normalizeColors(merged.colors);
|
||||
return {
|
||||
...colors,
|
||||
background: colors.base00,
|
||||
subtleBg: colors.base01,
|
||||
selection: colors.base02,
|
||||
subtle: colors.base03,
|
||||
muted: colors.base04,
|
||||
text: colors.base05,
|
||||
primary: colors.base0d,
|
||||
success: colors.base0b,
|
||||
warning: colors.base09,
|
||||
error: colors.base08,
|
||||
nerdFonts: merged.nerdFonts,
|
||||
emojiFonts: merged.emojiFonts,
|
||||
reducedMotion: merged.reducedMotion,
|
||||
mouse: merged.mouse,
|
||||
borderStyle: resolveBorderStyle(merged.border)
|
||||
};
|
||||
}
|
||||
var theme = buildTheme();
|
||||
var presets = buildPresets(theme);
|
||||
|
||||
export { theme, presets };
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
// @bun
|
||||
// src/yaml.ts
|
||||
import { homedir } from "os";
|
||||
import { join } from "path";
|
||||
function configPath2() {
|
||||
const configDir = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
||||
return join(configDir, "ilovetui", "config.yaml");
|
||||
}
|
||||
|
||||
export { configPath2 };
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
// @bun
|
||||
// src/presets.ts
|
||||
function buildPresets(theme) {
|
||||
function buildPresets2(theme) {
|
||||
const select = {
|
||||
backgroundColor: theme.background,
|
||||
textColor: theme.text,
|
||||
@@ -41,4 +41,4 @@ function buildPresets(theme) {
|
||||
return { select, tabSelect, input, textarea: input, box, slider };
|
||||
}
|
||||
|
||||
export { buildPresets };
|
||||
export { buildPresets2 };
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
// @bun
|
||||
import {
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
theme2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/components/ProgressBar.tsx
|
||||
import { effect as _$effect } from "@opentui/solid";
|
||||
@@ -23,9 +23,9 @@ function sampleGradient(stops, t) {
|
||||
const [br, bg, bb, ba] = parseColor(stops[i + 1]).toInts();
|
||||
return RGBA.fromInts(Math.round(ar + (br - ar) * localT), Math.round(ag + (bg - ag) * localT), Math.round(ab + (bb - ab) * localT), Math.round(aa + (ba - aa) * localT));
|
||||
}
|
||||
function ProgressBar(props) {
|
||||
function ProgressBar2(props) {
|
||||
const width = () => props.width ?? 20;
|
||||
const stops = createMemo(() => Array.isArray(props.color) ? props.color : [props.color ?? theme.primary]);
|
||||
const stops = createMemo(() => Array.isArray(props.color) ? props.color : [props.color ?? theme2.primary]);
|
||||
const filled = createMemo(() => Math.round(width() * Math.min(Math.max(props.value, 0), 100) / 100));
|
||||
const cells = createMemo(() => Array.from({
|
||||
length: width()
|
||||
@@ -46,7 +46,7 @@ function ProgressBar(props) {
|
||||
var _c$ = _$memo(() => !!isFilled());
|
||||
return () => _c$() ? props.fillChar ?? "\u2588" : props.trackChar ?? "\u2591";
|
||||
})());
|
||||
_$effect((_$p) => _$setProp(_el$2, "fg", isFilled() ? sampleGradient(stops(), t) : props.trackColor ?? theme.muted, _$p));
|
||||
_$effect((_$p) => _$setProp(_el$2, "fg", isFilled() ? sampleGradient(stops(), t) : props.trackColor ?? theme2.muted, _$p));
|
||||
return _el$2;
|
||||
})();
|
||||
}
|
||||
@@ -55,4 +55,4 @@ function ProgressBar(props) {
|
||||
})();
|
||||
}
|
||||
|
||||
export { ProgressBar };
|
||||
export { ProgressBar2 };
|
||||
Vendored
-100
@@ -1,100 +0,0 @@
|
||||
// @bun
|
||||
import {
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
|
||||
// src/components/Modal.tsx
|
||||
import { insertNode as _$insertNode } from "@opentui/solid";
|
||||
import { createComponent as _$createComponent } 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 { RGBA } from "@opentui/core";
|
||||
import { Portal, useTerminalDimensions } from "@opentui/solid";
|
||||
import { Show } from "solid-js";
|
||||
function Modal(props) {
|
||||
const dimensions = useTerminalDimensions();
|
||||
const screenCap = () => Math.max(5, dimensions().height - 4);
|
||||
const maxPanelHeight = () => Math.min(props.maxHeight ?? screenCap(), screenCap());
|
||||
const maxContentHeight = () => Math.max(1, maxPanelHeight() - 4);
|
||||
const maxPanelWidth = () => Math.max(10, dimensions().width - 4);
|
||||
const panelWidth = () => Math.min(props.width ?? 50, maxPanelWidth());
|
||||
return _$createComponent(Portal, {
|
||||
get children() {
|
||||
return _$createComponent(Show, {
|
||||
get when() {
|
||||
return props.open;
|
||||
},
|
||||
get children() {
|
||||
var _el$ = _$createElement("box"), _el$2 = _$createElement("box");
|
||||
_$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", 2000);
|
||||
_$setProp(_el$2, "border", true);
|
||||
_$setProp(_el$2, "flexDirection", "column");
|
||||
_$setProp(_el$2, "paddingLeft", 2);
|
||||
_$setProp(_el$2, "paddingRight", 2);
|
||||
_$setProp(_el$2, "paddingTop", 1);
|
||||
_$setProp(_el$2, "paddingBottom", 1);
|
||||
_$insert(_el$2, _$createComponent(Show, {
|
||||
get when() {
|
||||
return props.scrollable;
|
||||
},
|
||||
get fallback() {
|
||||
return (() => {
|
||||
var _el$4 = _$createElement("box");
|
||||
_$setProp(_el$4, "flexDirection", "column");
|
||||
_$setProp(_el$4, "overflow", "hidden");
|
||||
_$insert(_el$4, () => props.children);
|
||||
_$effect((_$p) => _$setProp(_el$4, "maxHeight", maxContentHeight(), _$p));
|
||||
return _el$4;
|
||||
})();
|
||||
},
|
||||
get children() {
|
||||
var _el$3 = _$createElement("scrollbox");
|
||||
_$setProp(_el$3, "flexShrink", 1);
|
||||
_$setProp(_el$3, "scrollY", true);
|
||||
_$setProp(_el$3, "scrollX", false);
|
||||
_$insert(_el$3, () => props.children);
|
||||
_$effect((_$p) => _$setProp(_el$3, "maxHeight", maxContentHeight(), _$p));
|
||||
return _el$3;
|
||||
}
|
||||
}));
|
||||
_$effect((_p$) => {
|
||||
var _v$ = dimensions().width, _v$2 = dimensions().height, _v$3 = props.backdropColor ?? RGBA.fromInts(0, 0, 0, 150), _v$4 = theme.mouse ? props.onDismiss : undefined, _v$5 = panelWidth(), _v$6 = maxPanelHeight(), _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$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$, "onMouseDown", _v$4, _p$.o));
|
||||
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$2, "width", _v$5, _p$.i));
|
||||
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$2, "maxHeight", _v$6, _p$.n));
|
||||
_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$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$;
|
||||
}, {
|
||||
e: undefined,
|
||||
t: undefined,
|
||||
a: undefined,
|
||||
o: undefined,
|
||||
i: undefined,
|
||||
n: undefined,
|
||||
s: undefined,
|
||||
h: undefined,
|
||||
r: undefined,
|
||||
d: undefined
|
||||
});
|
||||
return _el$;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { Modal };
|
||||
Vendored
+8
-7
@@ -1,11 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
Badge
|
||||
} from "../chunk-bscbaa87.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
Badge2
|
||||
} from "../chunk-v9cetqzr.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
Badge
|
||||
Badge2 as Badge
|
||||
};
|
||||
|
||||
Vendored
+8
-7
@@ -1,11 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
Box
|
||||
} from "../chunk-ynqvwj4d.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
Box2
|
||||
} from "../chunk-8r4rajaz.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
Box
|
||||
Box2 as Box
|
||||
};
|
||||
|
||||
Vendored
+8
-7
@@ -1,11 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
HelpBar
|
||||
} from "../chunk-rvq53a2a.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
HelpBar2
|
||||
} from "../chunk-c1dx8cn3.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
HelpBar
|
||||
HelpBar2 as HelpBar
|
||||
};
|
||||
|
||||
Vendored
+10
-9
@@ -1,13 +1,14 @@
|
||||
// @bun
|
||||
import {
|
||||
HelpModal
|
||||
} from "../chunk-xxg67vfz.js";
|
||||
import"../chunk-zjfxbvmq.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
import"../chunk-cyvpvpws.js";
|
||||
HelpModal2
|
||||
} from "../chunk-ersd5rrs.js";
|
||||
import"../chunk-t2cszvff.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
import"../chunk-ee2qaswx.js";
|
||||
export {
|
||||
HelpModal
|
||||
HelpModal2 as HelpModal
|
||||
};
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
KeyValue2
|
||||
} from "../chunk-c4gp0x16.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
KeyValue2 as KeyValue
|
||||
};
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
ListRow2
|
||||
} from "../chunk-sh9n4zha.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
ListRow2 as ListRow
|
||||
};
|
||||
Vendored
+8
-7
@@ -1,11 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
MinSizeGuard
|
||||
} from "../chunk-eta27h3c.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
MinSizeGuard2
|
||||
} from "../chunk-qdyd8q9r.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
MinSizeGuard
|
||||
MinSizeGuard2 as MinSizeGuard
|
||||
};
|
||||
|
||||
Vendored
+8
-7
@@ -1,11 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
Modal
|
||||
} from "../chunk-zjfxbvmq.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
Modal2
|
||||
} from "../chunk-t2cszvff.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
Modal
|
||||
Modal2 as Modal
|
||||
};
|
||||
|
||||
Vendored
+8
-8
@@ -1,12 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
NotificationHost
|
||||
} from "../chunk-fqzxyvhv.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
import"../chunk-y6txxzxk.js";
|
||||
NotificationHost2
|
||||
} from "../chunk-ajb193xj.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
NotificationHost
|
||||
NotificationHost2 as NotificationHost
|
||||
};
|
||||
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
// @bun
|
||||
import {
|
||||
Pane2,
|
||||
usePane2
|
||||
} from "../chunk-vazj633p.js";
|
||||
import"../chunk-59dzyjgd.js";
|
||||
export {
|
||||
Pane2 as Pane,
|
||||
usePane2 as usePane
|
||||
};
|
||||
Vendored
+8
-7
@@ -1,11 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
ProgressBar
|
||||
} from "../chunk-5m64a9kb.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
ProgressBar2
|
||||
} from "../chunk-z734n0wr.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
ProgressBar
|
||||
ProgressBar2 as ProgressBar
|
||||
};
|
||||
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
// @bun
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
import {
|
||||
theme2
|
||||
} from "../chunk-p6jvscj3.js";
|
||||
import {
|
||||
Spinner2
|
||||
} from "../chunk-kmqqzz9e.js";
|
||||
|
||||
// src/components/SectionHeader.tsx
|
||||
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
||||
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 { memo as _$memo } 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 SectionHeader(props) {
|
||||
return (() => {
|
||||
var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("box");
|
||||
_$insertNode(_el$, _el$2);
|
||||
_$insertNode(_el$, _el$4);
|
||||
_$setProp(_el$, "flexShrink", 0);
|
||||
_$setProp(_el$, "flexDirection", "row");
|
||||
_$setProp(_el$, "justifyContent", "space-between");
|
||||
_$setProp(_el$, "alignItems", "center");
|
||||
_$setProp(_el$, "height", 1);
|
||||
_$insertNode(_el$2, _el$3);
|
||||
_$setProp(_el$2, "flexGrow", 1);
|
||||
_$setProp(_el$2, "flexShrink", 1);
|
||||
_$setProp(_el$2, "minWidth", 0);
|
||||
_$setProp(_el$2, "marginRight", 1);
|
||||
_$setProp(_el$2, "overflow", "hidden");
|
||||
_$setProp(_el$3, "wrapMode", "none");
|
||||
_$insert(_el$3, () => props.title ?? "");
|
||||
_$setProp(_el$4, "flexShrink", 0);
|
||||
_$insert(_el$4, _$createComponent(Show, {
|
||||
get when() {
|
||||
return !props.loading;
|
||||
},
|
||||
get fallback() {
|
||||
return (() => {
|
||||
var _el$6 = _$createElement("box"), _el$7 = _$createElement("text"), _el$8 = _$createTextNode(` `);
|
||||
_$insertNode(_el$6, _el$7);
|
||||
_$setProp(_el$6, "flexDirection", "row");
|
||||
_$setProp(_el$6, "alignItems", "center");
|
||||
_$insert(_el$6, _$createComponent(Spinner2, {}), _el$7);
|
||||
_$insertNode(_el$7, _el$8);
|
||||
_$insert(_el$7, () => props.loadingLabel ?? "Loading\u2026", null);
|
||||
_$effect((_$p) => _$setProp(_el$7, "fg", theme2.muted, _$p));
|
||||
return _el$6;
|
||||
})();
|
||||
},
|
||||
get children() {
|
||||
var _el$5 = _$createElement("text");
|
||||
_$insert(_el$5, () => props.hint ?? "");
|
||||
_$effect((_$p) => _$setProp(_el$5, "fg", theme2.muted, _$p));
|
||||
return _el$5;
|
||||
}
|
||||
}));
|
||||
_$effect((_p$) => {
|
||||
var _v$ = theme2.muted, _v$2 = TextAttributes.BOLD;
|
||||
_v$ !== _p$.e && (_p$.e = _$setProp(_el$3, "fg", _v$, _p$.e));
|
||||
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$3, "attributes", _v$2, _p$.t));
|
||||
return _p$;
|
||||
}, {
|
||||
e: undefined,
|
||||
t: undefined
|
||||
});
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
export {
|
||||
SectionHeader
|
||||
};
|
||||
Vendored
+8
-7
@@ -1,11 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
Sidebar
|
||||
} from "../chunk-15vweq1q.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
Sidebar2
|
||||
} from "../chunk-rcywynbw.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
Sidebar
|
||||
Sidebar2 as Sidebar
|
||||
};
|
||||
|
||||
Vendored
+8
-7
@@ -1,11 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
SignalBars
|
||||
} from "../chunk-dctn29v0.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
SignalBars2
|
||||
} from "../chunk-tz3vvffs.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
SignalBars
|
||||
SignalBars2 as SignalBars
|
||||
};
|
||||
|
||||
Vendored
+8
-35
@@ -1,39 +1,12 @@
|
||||
// @bun
|
||||
import {
|
||||
theme
|
||||
} from "../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
|
||||
// src/components/Spinner.tsx
|
||||
import { setProp as _$setProp } from "@opentui/solid";
|
||||
import { effect as _$effect } from "@opentui/solid";
|
||||
import { createElement as _$createElement } from "@opentui/solid";
|
||||
import"opentui-spinner/solid";
|
||||
function Spinner(props) {
|
||||
return (() => {
|
||||
var _el$ = _$createElement("spinner");
|
||||
_$effect((_p$) => {
|
||||
var { name: _v$, frames: _v$2, interval: _v$3 } = props, _v$4 = props.autoplay ?? !theme.reducedMotion, _v$5 = props.color ?? theme.primary, _v$6 = props.backgroundColor;
|
||||
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "name", _v$, _p$.e));
|
||||
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "frames", _v$2, _p$.t));
|
||||
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$, "interval", _v$3, _p$.a));
|
||||
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$, "autoplay", _v$4, _p$.o));
|
||||
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$, "color", _v$5, _p$.i));
|
||||
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$, "backgroundColor", _v$6, _p$.n));
|
||||
return _p$;
|
||||
}, {
|
||||
e: undefined,
|
||||
t: undefined,
|
||||
a: undefined,
|
||||
o: undefined,
|
||||
i: undefined,
|
||||
n: undefined
|
||||
});
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
Spinner2
|
||||
} from "../chunk-kmqqzz9e.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
Spinner
|
||||
Spinner2 as Spinner
|
||||
};
|
||||
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
// @bun
|
||||
import {
|
||||
TabbedPanel2
|
||||
} from "../chunk-wtbg0e39.js";
|
||||
import"../chunk-vf2ybhtx.js";
|
||||
import"../chunk-8r4rajaz.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
TabbedPanel2 as TabbedPanel
|
||||
};
|
||||
Vendored
+10
-9
@@ -1,13 +1,14 @@
|
||||
// @bun
|
||||
import {
|
||||
Tabs,
|
||||
stepTabValue
|
||||
} from "../chunk-ev4wvhd2.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
stepTabValue2,
|
||||
Tabs2
|
||||
} from "../chunk-vf2ybhtx.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
stepTabValue,
|
||||
Tabs
|
||||
Tabs2 as Tabs,
|
||||
stepTabValue2 as stepTabValue
|
||||
};
|
||||
|
||||
Vendored
+9
-8
@@ -1,12 +1,13 @@
|
||||
// @bun
|
||||
import {
|
||||
Toggle
|
||||
} from "../chunk-mezyz219.js";
|
||||
import"../chunk-bscbaa87.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
Toggle2
|
||||
} from "../chunk-qhp6k176.js";
|
||||
import"../chunk-v9cetqzr.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
export {
|
||||
Toggle
|
||||
Toggle2 as Toggle
|
||||
};
|
||||
|
||||
Vendored
+13
-12
@@ -1,17 +1,18 @@
|
||||
// @bun
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import {
|
||||
HelpModal
|
||||
} from "../chunk-xxg67vfz.js";
|
||||
import"../chunk-zjfxbvmq.js";
|
||||
HelpBar2
|
||||
} from "../chunk-c1dx8cn3.js";
|
||||
import"../chunk-ee2qaswx.js";
|
||||
import"../chunk-t2cszvff.js";
|
||||
import {
|
||||
HelpBar
|
||||
} from "../chunk-rvq53a2a.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
import"../chunk-cyvpvpws.js";
|
||||
HelpModal2
|
||||
} from "../chunk-ersd5rrs.js";
|
||||
export {
|
||||
HelpModal,
|
||||
HelpBar
|
||||
HelpBar2 as HelpBar,
|
||||
HelpModal2 as HelpModal
|
||||
};
|
||||
|
||||
Vendored
+56
-37
@@ -1,50 +1,69 @@
|
||||
// @bun
|
||||
import"../chunk-njvj322f.js";
|
||||
import"../chunk-6ggk5fg7.js";
|
||||
import"../chunk-yyg967q8.js";
|
||||
import"../chunk-xykm10y9.js";
|
||||
import"../chunk-p6jvscj3.js";
|
||||
import {
|
||||
NotificationHost
|
||||
} from "../chunk-fqzxyvhv.js";
|
||||
Badge2
|
||||
} from "../chunk-v9cetqzr.js";
|
||||
import {
|
||||
Sidebar
|
||||
} from "../chunk-15vweq1q.js";
|
||||
Box2
|
||||
} from "../chunk-8r4rajaz.js";
|
||||
import {
|
||||
Toggle
|
||||
} from "../chunk-mezyz219.js";
|
||||
KeyValue2
|
||||
} from "../chunk-c4gp0x16.js";
|
||||
import {
|
||||
MinSizeGuard
|
||||
} from "../chunk-eta27h3c.js";
|
||||
ListRow2
|
||||
} from "../chunk-sh9n4zha.js";
|
||||
import {
|
||||
Tabs,
|
||||
stepTabValue
|
||||
} from "../chunk-ev4wvhd2.js";
|
||||
MinSizeGuard2
|
||||
} from "../chunk-qdyd8q9r.js";
|
||||
import {
|
||||
Box
|
||||
} from "../chunk-ynqvwj4d.js";
|
||||
Modal2
|
||||
} from "../chunk-t2cszvff.js";
|
||||
import {
|
||||
Modal
|
||||
} from "../chunk-zjfxbvmq.js";
|
||||
NotificationHost2
|
||||
} from "../chunk-ajb193xj.js";
|
||||
import"../chunk-59dzyjgd.js";
|
||||
import {
|
||||
Badge
|
||||
} from "../chunk-bscbaa87.js";
|
||||
Pane2,
|
||||
usePane2
|
||||
} from "../chunk-vazj633p.js";
|
||||
import {
|
||||
ProgressBar
|
||||
} from "../chunk-5m64a9kb.js";
|
||||
ProgressBar2
|
||||
} from "../chunk-z734n0wr.js";
|
||||
import {
|
||||
SignalBars
|
||||
} from "../chunk-dctn29v0.js";
|
||||
import"../chunk-x7m82z3z.js";
|
||||
import"../chunk-7f8jagy5.js";
|
||||
import"../chunk-hcq62p48.js";
|
||||
import"../chunk-cnhhssmz.js";
|
||||
import"../chunk-y6txxzxk.js";
|
||||
Sidebar2
|
||||
} from "../chunk-rcywynbw.js";
|
||||
import {
|
||||
SignalBars2
|
||||
} from "../chunk-tz3vvffs.js";
|
||||
import {
|
||||
stepTabValue2,
|
||||
Tabs2
|
||||
} from "../chunk-vf2ybhtx.js";
|
||||
import {
|
||||
TabbedPanel2
|
||||
} from "../chunk-wtbg0e39.js";
|
||||
import {
|
||||
Toggle2
|
||||
} from "../chunk-qhp6k176.js";
|
||||
export {
|
||||
stepTabValue,
|
||||
Toggle,
|
||||
Tabs,
|
||||
SignalBars,
|
||||
Sidebar,
|
||||
ProgressBar,
|
||||
NotificationHost,
|
||||
Modal,
|
||||
MinSizeGuard,
|
||||
Box,
|
||||
Badge
|
||||
Badge2 as Badge,
|
||||
Box2 as Box,
|
||||
KeyValue2 as KeyValue,
|
||||
ListRow2 as ListRow,
|
||||
MinSizeGuard2 as MinSizeGuard,
|
||||
Modal2 as Modal,
|
||||
NotificationHost2 as NotificationHost,
|
||||
Pane2 as Pane,
|
||||
ProgressBar2 as ProgressBar,
|
||||
Sidebar2 as Sidebar,
|
||||
SignalBars2 as SignalBars,
|
||||
TabbedPanel2 as TabbedPanel,
|
||||
Tabs2 as Tabs,
|
||||
Toggle2 as Toggle,
|
||||
stepTabValue2 as stepTabValue,
|
||||
usePane2 as usePane
|
||||
};
|
||||
|
||||
Vendored
+13
-13
@@ -1,17 +1,17 @@
|
||||
// @bun
|
||||
import {
|
||||
COLOR_KEYS,
|
||||
mergeColors,
|
||||
mergeConfig,
|
||||
normalizeColor,
|
||||
normalizeColors,
|
||||
resolveBorderStyle
|
||||
} from "./chunk-hcq62p48.js";
|
||||
COLOR_KEYS2,
|
||||
OPENTUI_BORDER_STYLES2,
|
||||
ThemeConfigSchema2,
|
||||
layerConfigs2,
|
||||
resolveConfig2,
|
||||
normalizeColor2
|
||||
} from "./chunk-njvj322f.js";
|
||||
export {
|
||||
resolveBorderStyle,
|
||||
normalizeColors,
|
||||
normalizeColor,
|
||||
mergeConfig,
|
||||
mergeColors,
|
||||
COLOR_KEYS
|
||||
COLOR_KEYS2 as COLOR_KEYS,
|
||||
OPENTUI_BORDER_STYLES2 as OPENTUI_BORDER_STYLES,
|
||||
ThemeConfigSchema2 as ThemeConfigSchema,
|
||||
layerConfigs2 as layerConfigs,
|
||||
normalizeColor2 as normalizeColor,
|
||||
resolveConfig2 as resolveConfig
|
||||
};
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// @bun
|
||||
import {
|
||||
isInside2,
|
||||
useFocusWithin2
|
||||
} from "../chunk-59dzyjgd.js";
|
||||
export {
|
||||
isInside2 as isInside,
|
||||
useFocusWithin2 as useFocusWithin
|
||||
};
|
||||
Vendored
+5
-5
@@ -1,9 +1,9 @@
|
||||
// @bun
|
||||
import {
|
||||
helpOpen,
|
||||
toggleHelp
|
||||
} from "../chunk-cyvpvpws.js";
|
||||
helpOpen2,
|
||||
toggleHelp2
|
||||
} from "../chunk-ee2qaswx.js";
|
||||
export {
|
||||
toggleHelp,
|
||||
helpOpen
|
||||
helpOpen2 as helpOpen,
|
||||
toggleHelp2 as toggleHelp
|
||||
};
|
||||
|
||||
Vendored
+3
-3
@@ -1,7 +1,7 @@
|
||||
// @bun
|
||||
import {
|
||||
createSimpleContext
|
||||
} from "../chunk-2px9e43p.js";
|
||||
createSimpleContext2
|
||||
} from "../chunk-jza406qg.js";
|
||||
export {
|
||||
createSimpleContext
|
||||
createSimpleContext2 as createSimpleContext
|
||||
};
|
||||
|
||||
Vendored
+21
-15
@@ -1,21 +1,27 @@
|
||||
// @bun
|
||||
import {
|
||||
createSimpleContext
|
||||
} from "../chunk-2px9e43p.js";
|
||||
createSimpleContext2
|
||||
} from "../chunk-jza406qg.js";
|
||||
import {
|
||||
helpOpen,
|
||||
toggleHelp
|
||||
} from "../chunk-cyvpvpws.js";
|
||||
notifications2,
|
||||
notify2,
|
||||
dismiss2
|
||||
} from "../chunk-6ggk5fg7.js";
|
||||
import {
|
||||
dismiss,
|
||||
notifications,
|
||||
notify
|
||||
} from "../chunk-y6txxzxk.js";
|
||||
helpOpen2,
|
||||
toggleHelp2
|
||||
} from "../chunk-ee2qaswx.js";
|
||||
import {
|
||||
isInside2,
|
||||
useFocusWithin2
|
||||
} from "../chunk-59dzyjgd.js";
|
||||
export {
|
||||
toggleHelp,
|
||||
notify,
|
||||
notifications,
|
||||
helpOpen,
|
||||
dismiss,
|
||||
createSimpleContext
|
||||
createSimpleContext2 as createSimpleContext,
|
||||
dismiss2 as dismiss,
|
||||
helpOpen2 as helpOpen,
|
||||
isInside2 as isInside,
|
||||
notifications2 as notifications,
|
||||
notify2 as notify,
|
||||
toggleHelp2 as toggleHelp,
|
||||
useFocusWithin2 as useFocusWithin
|
||||
};
|
||||
|
||||
Vendored
+7
-7
@@ -1,11 +1,11 @@
|
||||
// @bun
|
||||
import {
|
||||
dismiss,
|
||||
notifications,
|
||||
notify
|
||||
} from "../chunk-y6txxzxk.js";
|
||||
notifications2,
|
||||
notify2,
|
||||
dismiss2
|
||||
} from "../chunk-6ggk5fg7.js";
|
||||
export {
|
||||
notify,
|
||||
notifications,
|
||||
dismiss
|
||||
dismiss2 as dismiss,
|
||||
notifications2 as notifications,
|
||||
notify2 as notify
|
||||
};
|
||||
|
||||
Vendored
+18
-10
@@ -1,15 +1,23 @@
|
||||
// @bun
|
||||
import {
|
||||
presets,
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
theme2,
|
||||
presets2,
|
||||
configureTheme2,
|
||||
icon2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
import"./chunk-yyg967q8.js";
|
||||
import {
|
||||
configPath
|
||||
} from "./chunk-7f8jagy5.js";
|
||||
import"./chunk-hcq62p48.js";
|
||||
import"./chunk-cnhhssmz.js";
|
||||
ThemeConfigSchema2
|
||||
} from "./chunk-njvj322f.js";
|
||||
import"./chunk-6ggk5fg7.js";
|
||||
import {
|
||||
configPath2
|
||||
} from "./chunk-xykm10y9.js";
|
||||
export {
|
||||
theme,
|
||||
presets,
|
||||
configPath
|
||||
ThemeConfigSchema2 as ThemeConfigSchema,
|
||||
configPath2 as configPath,
|
||||
configureTheme2 as configureTheme,
|
||||
icon2 as icon,
|
||||
presets2 as presets,
|
||||
theme2 as theme
|
||||
};
|
||||
|
||||
Vendored
+4
-4
@@ -19,9 +19,9 @@ function KeymapProvider(props) {
|
||||
});
|
||||
}
|
||||
export {
|
||||
useKeymapSelector,
|
||||
useKeymap,
|
||||
useBindings,
|
||||
KeymapProvider,
|
||||
reactiveMatcherFromSignal,
|
||||
KeymapProvider
|
||||
useBindings,
|
||||
useKeymap,
|
||||
useKeymapSelector
|
||||
};
|
||||
|
||||
Vendored
+3
-3
@@ -1,7 +1,7 @@
|
||||
// @bun
|
||||
import {
|
||||
buildPresets
|
||||
} from "./chunk-cnhhssmz.js";
|
||||
buildPresets2
|
||||
} from "./chunk-yyg967q8.js";
|
||||
export {
|
||||
buildPresets
|
||||
buildPresets2 as buildPresets
|
||||
};
|
||||
|
||||
Vendored
+47
-5
@@ -1,7 +1,7 @@
|
||||
// @bun
|
||||
import {
|
||||
notify
|
||||
} from "./chunk-y6txxzxk.js";
|
||||
notify2
|
||||
} from "./chunk-6ggk5fg7.js";
|
||||
|
||||
// src/project-config.ts
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
@@ -19,7 +19,7 @@ function readYaml(path) {
|
||||
function warn(projectName, path, message) {
|
||||
const text = `${path}: ${message}`;
|
||||
console.error(`[${projectName} config] ${text}`);
|
||||
notify(text, { kind: "warning", duration: 0 });
|
||||
notify2(text, { kind: "warning", duration: 0 });
|
||||
}
|
||||
function loadProjectConfig(options) {
|
||||
const { projectName, defaultConfigPath, defaultSchema, userSchema } = options;
|
||||
@@ -54,6 +54,47 @@ ${z.prettifyError(defaultsResult.error)}`);
|
||||
})();
|
||||
return { defaults: defaultsResult.data, user };
|
||||
}
|
||||
function isPlainObject(value) {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
function deepMerge(base, over) {
|
||||
if (!isPlainObject(base) || !isPlainObject(over))
|
||||
return over === undefined ? base : over;
|
||||
const out = { ...base };
|
||||
for (const [key, value] of Object.entries(over))
|
||||
out[key] = deepMerge(base[key], value);
|
||||
return out;
|
||||
}
|
||||
function loadMergedProjectConfig(options) {
|
||||
const { projectName, defaultConfigPath, schema } = options;
|
||||
let rawDefaults;
|
||||
try {
|
||||
rawDefaults = readYaml(defaultConfigPath);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read ${defaultConfigPath}: ${error instanceof Error ? error.message : error}`);
|
||||
}
|
||||
const defaults = schema.safeParse(rawDefaults);
|
||||
if (!defaults.success) {
|
||||
throw new Error(`${defaultConfigPath} is invalid:
|
||||
${z.prettifyError(defaults.error)}`);
|
||||
}
|
||||
const path = userConfigPath(projectName);
|
||||
if (!existsSync(path))
|
||||
return defaults.data;
|
||||
let rawUser;
|
||||
try {
|
||||
rawUser = readYaml(path);
|
||||
} catch (error) {
|
||||
warn(projectName, path, error instanceof Error ? error.message : String(error));
|
||||
return defaults.data;
|
||||
}
|
||||
const merged = schema.safeParse(deepMerge(rawDefaults, rawUser ?? {}));
|
||||
if (!merged.success) {
|
||||
warn(projectName, path, z.prettifyError(merged.error));
|
||||
return defaults.data;
|
||||
}
|
||||
return merged.data;
|
||||
}
|
||||
function keybindsSchema(names, required) {
|
||||
const shape = {};
|
||||
for (const name of names) {
|
||||
@@ -69,7 +110,8 @@ function mergeKeybinds(names, defaults, user) {
|
||||
return merged;
|
||||
}
|
||||
export {
|
||||
mergeKeybinds,
|
||||
keybindsSchema,
|
||||
loadMergedProjectConfig,
|
||||
loadProjectConfig,
|
||||
keybindsSchema
|
||||
mergeKeybinds
|
||||
};
|
||||
|
||||
Vendored
+19
-16
@@ -1,11 +1,12 @@
|
||||
// @bun
|
||||
import"./chunk-njvj322f.js";
|
||||
import"./chunk-6ggk5fg7.js";
|
||||
import"./chunk-yyg967q8.js";
|
||||
import"./chunk-xykm10y9.js";
|
||||
import {
|
||||
presets,
|
||||
theme
|
||||
} from "./chunk-x7m82z3z.js";
|
||||
import"./chunk-7f8jagy5.js";
|
||||
import"./chunk-hcq62p48.js";
|
||||
import"./chunk-cnhhssmz.js";
|
||||
theme2,
|
||||
presets2
|
||||
} from "./chunk-p6jvscj3.js";
|
||||
|
||||
// src/solid.ts
|
||||
import { extend } from "@opentui/solid";
|
||||
@@ -23,23 +24,25 @@ function withThemeDefaults(Ctor, defaults, after) {
|
||||
class Themed extends Ctor {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
Object.assign(this, defaults);
|
||||
Object.assign(this, defaults());
|
||||
after?.(this);
|
||||
}
|
||||
}
|
||||
return Themed;
|
||||
}
|
||||
var { borderStyle, borderColor, focusedBorderColor } = presets.box;
|
||||
var boxDefaults = { borderStyle, borderColor, focusedBorderColor };
|
||||
var boxDefaults = () => {
|
||||
const { borderStyle, borderColor, focusedBorderColor } = presets2.box;
|
||||
return { borderStyle, borderColor, focusedBorderColor };
|
||||
};
|
||||
extend({
|
||||
box: withThemeDefaults(BoxRenderable, boxDefaults, (instance) => {
|
||||
instance.border = false;
|
||||
}),
|
||||
select: withThemeDefaults(SelectRenderable, presets.select),
|
||||
tab_select: withThemeDefaults(TabSelectRenderable, presets.tabSelect),
|
||||
input: withThemeDefaults(InputRenderable, presets.input),
|
||||
textarea: withThemeDefaults(TextareaRenderable, presets.textarea),
|
||||
slider: withThemeDefaults(SliderRenderable, presets.slider),
|
||||
text: withThemeDefaults(TextRenderable, { fg: theme.text }),
|
||||
ascii_font: withThemeDefaults(ASCIIFontRenderable, { color: theme.primary })
|
||||
select: withThemeDefaults(SelectRenderable, () => presets2.select),
|
||||
tab_select: withThemeDefaults(TabSelectRenderable, () => presets2.tabSelect),
|
||||
input: withThemeDefaults(InputRenderable, () => presets2.input),
|
||||
textarea: withThemeDefaults(TextareaRenderable, () => presets2.textarea),
|
||||
slider: withThemeDefaults(SliderRenderable, () => presets2.slider),
|
||||
text: withThemeDefaults(TextRenderable, () => ({ fg: theme2.text })),
|
||||
ascii_font: withThemeDefaults(ASCIIFontRenderable, () => ({ color: theme2.primary }))
|
||||
});
|
||||
|
||||
Vendored
+3
-5
@@ -1,9 +1,7 @@
|
||||
// @bun
|
||||
import {
|
||||
configPath,
|
||||
readYamlFile
|
||||
} from "./chunk-7f8jagy5.js";
|
||||
configPath2
|
||||
} from "./chunk-xykm10y9.js";
|
||||
export {
|
||||
readYamlFile,
|
||||
configPath
|
||||
configPath2 as configPath
|
||||
};
|
||||
|
||||
+2
-1
@@ -12,7 +12,8 @@
|
||||
"./keymap": { "types": "./src/keymap/provider.tsx", "default": "./dist/keymap/provider.js" },
|
||||
"./components": { "types": "./src/components/index.ts", "default": "./dist/components/index.js" },
|
||||
"./components/spinner": { "types": "./src/components/Spinner.tsx", "default": "./dist/components/Spinner.js" },
|
||||
"./components/help": { "types": "./src/components/help.ts", "default": "./dist/components/help.js" }
|
||||
"./components/help": { "types": "./src/components/help.ts", "default": "./dist/components/help.js" },
|
||||
"./components/section-header": { "types": "./src/components/SectionHeader.tsx", "default": "./dist/components/SectionHeader.js" }
|
||||
},
|
||||
"private": true,
|
||||
"files": ["dist", "src"],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type ColorInput, TextAttributes } from "@opentui/core";
|
||||
import { type ColorInput, type Renderable, TextAttributes } from "@opentui/core";
|
||||
import { formatCommandBindings } from "@opentui/keymap/extras";
|
||||
import { useKeymapSelector } from "@opentui/keymap/solid";
|
||||
import { createSignal } from "solid-js";
|
||||
import { helpOpen, toggleHelp } from "../context/help.ts";
|
||||
import { Modal } from "./Modal.tsx";
|
||||
|
||||
@@ -11,6 +12,8 @@ export interface HelpModalProps {
|
||||
backgroundColor?: ColorInput;
|
||||
backdropColor?: ColorInput;
|
||||
width?: number;
|
||||
/** See ModalProps.dismissKey. */
|
||||
dismissKey?: string | false;
|
||||
}
|
||||
|
||||
interface HelpEntry {
|
||||
@@ -20,8 +23,13 @@ interface HelpEntry {
|
||||
}
|
||||
|
||||
export function HelpModal(props: HelpModalProps = {}) {
|
||||
// The modal takes focus when it opens, which would make its own "close"
|
||||
// the only active command. List what was active where focus was *before*
|
||||
// it opened instead.
|
||||
const [scope, setScope] = createSignal<Renderable | null>(null);
|
||||
|
||||
const entries = useKeymapSelector((keymap): HelpEntry[] =>
|
||||
keymap.getCommandEntries({ visibility: "active" }).map((entry) => ({
|
||||
keymap.getCommandEntries({ visibility: "active", focused: scope() }).map((entry) => ({
|
||||
command: entry.command.name,
|
||||
keys: formatCommandBindings(entry.bindings, { keyNameAliases: KEY_DISPLAY }) ?? "",
|
||||
label: typeof entry.command.desc === "string" ? entry.command.desc : entry.command.name,
|
||||
@@ -32,6 +40,8 @@ export function HelpModal(props: HelpModalProps = {}) {
|
||||
<Modal
|
||||
open={helpOpen()}
|
||||
onDismiss={toggleHelp}
|
||||
onOpen={(previous) => setScope(() => previous)}
|
||||
dismissKey={props.dismissKey}
|
||||
accentColor={props.accentColor}
|
||||
backgroundColor={props.backgroundColor}
|
||||
backdropColor={props.backdropColor}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ColorInput } from "@opentui/core";
|
||||
import { Show, type JSX } from "solid-js";
|
||||
import { theme } from "../index.ts";
|
||||
|
||||
export interface KeyValueProps {
|
||||
label: string;
|
||||
/** Plain value; ignored when children are given. */
|
||||
value?: string | number;
|
||||
valueColor?: ColorInput;
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
/** A "Label ........ value" line, as used in details panels and modals. */
|
||||
export function KeyValue(props: KeyValueProps) {
|
||||
return (
|
||||
<box flexShrink={0} flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.muted}>{props.label}</text>
|
||||
<Show when={props.children} fallback={<text fg={props.valueColor}>{props.value ?? ""}</text>}>
|
||||
{props.children}
|
||||
</Show>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { type ColorInput, TextAttributes } from "@opentui/core";
|
||||
import { Show, type JSX } from "solid-js";
|
||||
import { presets, theme } from "../index.ts";
|
||||
|
||||
/** Returns the selected-row text color when the row is selected, `fallback` otherwise. */
|
||||
export type RowTone = (fallback?: ColorInput) => ColorInput | undefined;
|
||||
|
||||
export interface ListRowProps {
|
||||
id?: string;
|
||||
label: string;
|
||||
selected: boolean;
|
||||
bold?: boolean;
|
||||
onClick?: () => void;
|
||||
/** Right-aligned content; use `tone` for its colors so it follows the selection. */
|
||||
right?: (tone: RowTone) => JSX.Element;
|
||||
/** Extra lines under the main one. */
|
||||
below?: (tone: RowTone) => JSX.Element;
|
||||
}
|
||||
|
||||
/** One row of a keyboard-navigable list: truncated label on the left, details on the right. */
|
||||
export function ListRow(props: ListRowProps) {
|
||||
const tone: RowTone = (fallback) => (props.selected ? presets.select.selectedTextColor : fallback);
|
||||
|
||||
return (
|
||||
<box
|
||||
id={props.id}
|
||||
flexShrink={0}
|
||||
flexDirection="column"
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
backgroundColor={props.selected ? presets.select.selectedBackgroundColor : "transparent"}
|
||||
onMouseDown={theme.mouse && props.onClick ? () => props.onClick?.() : undefined}
|
||||
>
|
||||
<box flexDirection="row" justifyContent="space-between" alignItems="center" height={1}>
|
||||
<box flexGrow={1} flexShrink={1} minWidth={0} marginRight={1} overflow="hidden">
|
||||
<text fg={tone()} attributes={props.bold ? TextAttributes.BOLD : undefined} wrapMode="none">
|
||||
{props.label}
|
||||
</text>
|
||||
</box>
|
||||
<Show when={props.right}>
|
||||
<box flexShrink={0} flexDirection="row" alignItems="center">
|
||||
{props.right?.(tone)}
|
||||
</box>
|
||||
</Show>
|
||||
</box>
|
||||
{props.below?.(tone)}
|
||||
</box>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,26 @@
|
||||
import { type ColorInput, type MouseEvent, RGBA } from "@opentui/core";
|
||||
import { Portal, useTerminalDimensions } from "@opentui/solid";
|
||||
import { Show, type ParentProps } from "solid-js";
|
||||
import { type BoxRenderable, type ColorInput, type KeyEvent, type MouseEvent, type Renderable, RGBA } from "@opentui/core";
|
||||
import type { Binding, Command } from "@opentui/keymap";
|
||||
import { useBindings } from "@opentui/keymap/solid";
|
||||
import { Portal, useRenderer, useTerminalDimensions } from "@opentui/solid";
|
||||
import { createSignal, onCleanup, onMount, Show, type ParentProps } from "solid-js";
|
||||
import { theme } from "../index.ts";
|
||||
|
||||
export type ModalCommand = Command<Renderable, KeyEvent>;
|
||||
export type ModalBinding = Binding<Renderable, KeyEvent>;
|
||||
|
||||
export interface ModalProps extends ParentProps {
|
||||
open: boolean;
|
||||
onDismiss?: () => void;
|
||||
/**
|
||||
* Key that calls onDismiss while the modal has focus (default "escape").
|
||||
* `false` disables it. Needs a KeymapProvider unless disabled.
|
||||
*/
|
||||
dismissKey?: string | false;
|
||||
/** Extra keymap commands/bindings, active only while the modal has focus. */
|
||||
commands?: ModalCommand[];
|
||||
bindings?: ModalBinding[];
|
||||
/** Called on open with whatever had focus before the modal took it. */
|
||||
onOpen?: (previousFocus: Renderable | null) => void;
|
||||
accentColor?: ColorInput;
|
||||
backgroundColor?: ColorInput;
|
||||
backdropColor?: ColorInput;
|
||||
@@ -15,16 +30,54 @@ export interface ModalProps extends ParentProps {
|
||||
}
|
||||
|
||||
export function Modal(props: ModalProps) {
|
||||
return (
|
||||
<Portal>
|
||||
<Show when={props.open}>
|
||||
<ModalPanel {...props} />
|
||||
</Show>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
// Mounted only while open: takes native focus on mount and hands it back to
|
||||
// whatever had it on unmount, so keymap layers scoped to the page underneath
|
||||
// go quiet while the modal is up and come back on their own afterwards.
|
||||
function ModalPanel(props: ModalProps) {
|
||||
const renderer = useRenderer();
|
||||
const dimensions = useTerminalDimensions();
|
||||
const [panel, setPanel] = createSignal<BoxRenderable>();
|
||||
|
||||
const screenCap = () => Math.max(5, dimensions().height - 4);
|
||||
const maxPanelHeight = () => Math.min(props.maxHeight ?? screenCap(), screenCap());
|
||||
const maxContentHeight = () => Math.max(1, maxPanelHeight() - 4);
|
||||
const maxPanelWidth = () => Math.max(10, dimensions().width - 4);
|
||||
const panelWidth = () => Math.min(props.width ?? 50, maxPanelWidth());
|
||||
|
||||
let previousFocus: Renderable | null = null;
|
||||
onMount(() => {
|
||||
previousFocus = renderer.currentFocusedRenderable;
|
||||
props.onOpen?.(previousFocus);
|
||||
panel()?.focus();
|
||||
});
|
||||
onCleanup(() => {
|
||||
if (previousFocus && !previousFocus.isDestroyed) previousFocus.focus();
|
||||
});
|
||||
|
||||
const dismissKey = props.dismissKey ?? "escape";
|
||||
if (dismissKey !== false || props.commands || props.bindings) {
|
||||
useBindings(() => ({
|
||||
target: panel,
|
||||
commands: [
|
||||
...(dismissKey !== false
|
||||
? [{ name: "modal_dismiss", desc: "close", shortHelp: true, run: () => props.onDismiss?.() }]
|
||||
: []),
|
||||
...(props.commands ?? []),
|
||||
],
|
||||
bindings: [...(dismissKey !== false ? [{ key: dismissKey, cmd: "modal_dismiss" }] : []), ...(props.bindings ?? [])],
|
||||
}));
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Show when={props.open}>
|
||||
<box
|
||||
width={dimensions().width}
|
||||
height={dimensions().height}
|
||||
@@ -38,11 +91,14 @@ export function Modal(props: ModalProps) {
|
||||
onMouseDown={theme.mouse ? props.onDismiss : undefined}
|
||||
>
|
||||
<box
|
||||
ref={setPanel}
|
||||
focusable
|
||||
width={panelWidth()}
|
||||
maxHeight={maxPanelHeight()}
|
||||
border
|
||||
borderStyle={theme.borderStyle}
|
||||
borderColor={props.accentColor ?? theme.primary}
|
||||
focusedBorderColor={props.accentColor ?? theme.primary}
|
||||
backgroundColor={props.backgroundColor ?? theme.background}
|
||||
flexDirection="column"
|
||||
paddingLeft={2}
|
||||
@@ -59,13 +115,11 @@ export function Modal(props: ModalProps) {
|
||||
</box>
|
||||
}
|
||||
>
|
||||
<scrollbox maxHeight={maxContentHeight()} flexShrink={1} scrollY scrollX={false}>
|
||||
<scrollbox maxHeight={maxContentHeight()} flexShrink={1} scrollY scrollX={false} focusable={false}>
|
||||
{props.children}
|
||||
</scrollbox>
|
||||
</Show>
|
||||
</box>
|
||||
</box>
|
||||
</Show>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { BoxRenderable } from "@opentui/core";
|
||||
import type { BoxProps } from "@opentui/solid";
|
||||
import { type Accessor, createContext, createSignal, splitProps, useContext } from "solid-js";
|
||||
import { useFocusWithin } from "../context/focus.ts";
|
||||
|
||||
export interface PaneHandle {
|
||||
/** The pane's root renderable — use it as a keymap layer `target`. */
|
||||
target: Accessor<BoxRenderable | undefined>;
|
||||
/** True while native focus is anywhere inside the pane. */
|
||||
focused: Accessor<boolean>;
|
||||
focus(): void;
|
||||
}
|
||||
|
||||
const PaneContext = createContext<PaneHandle>();
|
||||
|
||||
export interface PaneProps extends Omit<BoxProps, "ref"> {
|
||||
/** Receives the pane's handle, e.g. to focus it or target it from outside. */
|
||||
handle?: (handle: PaneHandle) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A focus region. Wrap each independently focusable area (sidebar, content,
|
||||
* …) in a Pane; anything inside can call usePane() to scope keymap layers to
|
||||
* it (`useBindings(() => ({ target: pane.target, … }))`) and to style itself
|
||||
* from `pane.focused()`. Pass `focusable` when the pane itself should be the
|
||||
* focus target (rather than e.g. a <select> inside it).
|
||||
*/
|
||||
export function Pane(props: PaneProps) {
|
||||
const [local, rest] = splitProps(props, ["handle", "children"]);
|
||||
const [el, setEl] = createSignal<BoxRenderable>();
|
||||
const focused = useFocusWithin(el);
|
||||
const handle: PaneHandle = { target: el, focused, focus: () => el()?.focus() };
|
||||
local.handle?.(handle);
|
||||
|
||||
return (
|
||||
<PaneContext.Provider value={handle}>
|
||||
<box {...rest} ref={setEl}>
|
||||
{local.children}
|
||||
</box>
|
||||
</PaneContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function usePane(): PaneHandle {
|
||||
const pane = useContext(PaneContext);
|
||||
if (!pane) throw new Error("usePane() must be used inside a <Pane>");
|
||||
return pane;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { TextAttributes } from "@opentui/core";
|
||||
import { Show } from "solid-js";
|
||||
import { theme } from "../index.ts";
|
||||
import { Spinner } from "./Spinner.tsx";
|
||||
|
||||
export interface SectionHeaderProps {
|
||||
title?: string;
|
||||
loading?: boolean;
|
||||
/** Shown next to the spinner while loading (default "Loading…"). */
|
||||
loadingLabel?: string;
|
||||
/** Shown on the right when not loading, e.g. "r refresh". */
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
// Lives in its own entry point (ilovetui/components/section-header) because
|
||||
// it pulls in the optional opentui-spinner peer dependency.
|
||||
export function SectionHeader(props: SectionHeaderProps) {
|
||||
return (
|
||||
<box flexShrink={0} flexDirection="row" justifyContent="space-between" alignItems="center" height={1}>
|
||||
<box flexGrow={1} flexShrink={1} minWidth={0} marginRight={1} overflow="hidden">
|
||||
<text fg={theme.muted} attributes={TextAttributes.BOLD} wrapMode="none">
|
||||
{props.title ?? ""}
|
||||
</text>
|
||||
</box>
|
||||
<box flexShrink={0}>
|
||||
<Show
|
||||
when={!props.loading}
|
||||
fallback={
|
||||
<box flexDirection="row" alignItems="center">
|
||||
<Spinner />
|
||||
<text fg={theme.muted}> {props.loadingLabel ?? "Loading…"}</text>
|
||||
</box>
|
||||
}
|
||||
>
|
||||
<text fg={theme.muted}>{props.hint ?? ""}</text>
|
||||
</Show>
|
||||
</box>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type ColorInput, type MouseEvent, type SelectOption, type SelectRenderable, TextAttributes } from "@opentui/core";
|
||||
import { Show } from "solid-js";
|
||||
import { createEffect, Show } from "solid-js";
|
||||
import { presets, theme } from "../index.ts";
|
||||
|
||||
export interface SidebarHandle {
|
||||
@@ -27,6 +27,8 @@ function itemIndexAtScreenY(el: SelectRenderable, screenY: number): number | nul
|
||||
export interface SidebarProps {
|
||||
title?: string;
|
||||
items: SelectOption[];
|
||||
/** Value of the item to keep highlighted (e.g. the current route), if controlled. */
|
||||
value?: string;
|
||||
width?: number;
|
||||
focused?: boolean;
|
||||
onSelect: (option: SelectOption) => void;
|
||||
@@ -83,6 +85,12 @@ export function Sidebar(props: SidebarProps) {
|
||||
else if (event.scroll.direction === "up") select.moveUp(steps);
|
||||
};
|
||||
|
||||
// A user effect, so it runs after the `options` prop has been applied.
|
||||
createEffect(() => {
|
||||
const index = props.items.findIndex((item) => item.value === props.value);
|
||||
if (select && index !== -1 && select.getSelectedIndex() !== index) select.setSelectedIndex(index);
|
||||
});
|
||||
|
||||
return (
|
||||
<box
|
||||
width={props.width ?? 24}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { ParentProps } from "solid-js";
|
||||
import { theme } from "../index.ts";
|
||||
import { Box } from "./Box.tsx";
|
||||
import { type TabItem, Tabs } from "./Tabs.tsx";
|
||||
|
||||
export interface TabbedPanelProps extends ParentProps {
|
||||
items: TabItem[];
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
focused?: boolean;
|
||||
}
|
||||
|
||||
/** <Tabs/> on top of a bordered panel whose top edge is drawn by the tabs. */
|
||||
export function TabbedPanel(props: TabbedPanelProps) {
|
||||
return (
|
||||
<box flexDirection="column" flexGrow={1}>
|
||||
<Tabs items={props.items} value={props.value} onChange={props.onChange} focused={props.focused} />
|
||||
<Box
|
||||
flexGrow={1}
|
||||
border={["left", "right", "bottom"]}
|
||||
borderColor={props.focused ? theme.primary : theme.muted}
|
||||
flexDirection="column"
|
||||
>
|
||||
{props.children}
|
||||
</Box>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
export { Badge, type BadgeProps } from "./Badge.tsx";
|
||||
export { Box, type BoxProps } from "./Box.tsx";
|
||||
export { KeyValue, type KeyValueProps } from "./KeyValue.tsx";
|
||||
export { ListRow, type ListRowProps, type RowTone } from "./ListRow.tsx";
|
||||
export { MinSizeGuard, type MinSizeGuardProps } from "./MinSizeGuard.tsx";
|
||||
export { Modal, type ModalProps } from "./Modal.tsx";
|
||||
export { Modal, type ModalBinding, type ModalCommand, type ModalProps } from "./Modal.tsx";
|
||||
export { NotificationHost, type NotificationHostProps } from "./NotificationHost.tsx";
|
||||
export { Pane, usePane, type PaneHandle, type PaneProps } from "./Pane.tsx";
|
||||
export { ProgressBar, type ProgressBarProps } from "./ProgressBar.tsx";
|
||||
export { Sidebar, type SidebarHandle, type SidebarProps } from "./Sidebar.tsx";
|
||||
export { SignalBars, type SignalBarsProps } from "./SignalBars.tsx";
|
||||
export { TabbedPanel, type TabbedPanelProps } from "./TabbedPanel.tsx";
|
||||
export { stepTabValue, Tabs, type TabItem, type TabsProps } from "./Tabs.tsx";
|
||||
export { Toggle, type ToggleProps } from "./Toggle.tsx";
|
||||
|
||||
+48
-70
@@ -1,30 +1,4 @@
|
||||
export interface ColorsYAML {
|
||||
base00?: string;
|
||||
base01?: string;
|
||||
base02?: string;
|
||||
base03?: string;
|
||||
base04?: string;
|
||||
base05?: string;
|
||||
base06?: string;
|
||||
base07?: string;
|
||||
base08?: string;
|
||||
base09?: string;
|
||||
base0a?: string;
|
||||
base0b?: string;
|
||||
base0c?: string;
|
||||
base0d?: string;
|
||||
base0e?: string;
|
||||
base0f?: string;
|
||||
}
|
||||
|
||||
export interface ConfigYAML {
|
||||
nerd_fonts?: boolean;
|
||||
emoji_fonts?: boolean;
|
||||
reduced_motion?: boolean;
|
||||
mouse?: boolean;
|
||||
border?: string;
|
||||
colors?: ColorsYAML;
|
||||
}
|
||||
import { z } from "zod";
|
||||
|
||||
export const COLOR_KEYS = [
|
||||
"base00",
|
||||
@@ -45,39 +19,64 @@ export const COLOR_KEYS = [
|
||||
"base0f",
|
||||
] as const;
|
||||
|
||||
export type ResolvedColors = Record<(typeof COLOR_KEYS)[number], string>;
|
||||
export type ColorKey = (typeof COLOR_KEYS)[number];
|
||||
|
||||
export function mergeColors(
|
||||
base: ColorsYAML,
|
||||
user: ColorsYAML,
|
||||
): ResolvedColors {
|
||||
const merged = {} as ResolvedColors;
|
||||
for (const key of COLOR_KEYS) {
|
||||
merged[key] = user[key] || base[key] || "";
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
export const OPENTUI_BORDER_STYLES = ["single", "double", "rounded", "heavy"] as const;
|
||||
|
||||
export type OpenTUIBorderStyle = (typeof OPENTUI_BORDER_STYLES)[number];
|
||||
|
||||
const hexColor = z.string().regex(/^#?[0-9a-fA-F]{6}$/, 'expected a hex color like "#1e1e2e"');
|
||||
|
||||
const colorsShape = {} as Record<ColorKey, z.ZodOptional<z.ZodString>>;
|
||||
for (const key of COLOR_KEYS) colorsShape[key] = hexColor.optional();
|
||||
|
||||
// Not .strict(): ~/.config/ilovetui/config.yaml is shared by every
|
||||
// ilovetui-based TUI, and some of them read extra keys of their own from it
|
||||
// (e.g. `layout_border`). Unknown keys are stripped, known ones validated.
|
||||
export const ThemeConfigSchema = z.object({
|
||||
nerd_fonts: z.boolean().optional(),
|
||||
emoji_fonts: z.boolean().optional(),
|
||||
reduced_motion: z.boolean().optional(),
|
||||
mouse: z.boolean().optional(),
|
||||
border: z.enum(OPENTUI_BORDER_STYLES).optional(),
|
||||
colors: z.object(colorsShape).optional(),
|
||||
});
|
||||
|
||||
export type ConfigYAML = z.infer<typeof ThemeConfigSchema>;
|
||||
export type ColorsYAML = NonNullable<ConfigYAML["colors"]>;
|
||||
|
||||
export type ResolvedColors = Record<ColorKey, string>;
|
||||
|
||||
export interface ResolvedConfig {
|
||||
nerdFonts: boolean;
|
||||
emojiFonts: boolean;
|
||||
reducedMotion: boolean;
|
||||
mouse: boolean;
|
||||
border: string;
|
||||
border: OpenTUIBorderStyle;
|
||||
colors: ResolvedColors;
|
||||
}
|
||||
|
||||
export function mergeConfig(
|
||||
base: ConfigYAML,
|
||||
user: ConfigYAML,
|
||||
): ResolvedConfig {
|
||||
// Later layers win, key by key (colors included).
|
||||
export function layerConfigs(layers: ConfigYAML[]): ConfigYAML {
|
||||
const out: ConfigYAML = {};
|
||||
for (const layer of layers) {
|
||||
const { colors, ...rest } = layer;
|
||||
Object.assign(out, rest);
|
||||
if (colors) out.colors = { ...out.colors, ...colors };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function resolveConfig(config: ConfigYAML): ResolvedConfig {
|
||||
const colors = {} as ResolvedColors;
|
||||
for (const key of COLOR_KEYS) colors[key] = normalizeColor(config.colors?.[key] ?? "");
|
||||
return {
|
||||
nerdFonts: user.nerd_fonts ?? base.nerd_fonts ?? false,
|
||||
emojiFonts: user.emoji_fonts ?? base.emoji_fonts ?? false,
|
||||
reducedMotion: user.reduced_motion ?? base.reduced_motion ?? false,
|
||||
mouse: user.mouse ?? base.mouse ?? true,
|
||||
border: user.border || base.border || "rounded",
|
||||
colors: mergeColors(base.colors ?? {}, user.colors ?? {}),
|
||||
nerdFonts: config.nerd_fonts ?? false,
|
||||
emojiFonts: config.emoji_fonts ?? false,
|
||||
reducedMotion: config.reduced_motion ?? false,
|
||||
mouse: config.mouse ?? true,
|
||||
border: config.border ?? "rounded",
|
||||
colors,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,24 +85,3 @@ export function normalizeColor(hex: string): string {
|
||||
if (trimmed && !trimmed.startsWith("#")) return `#${trimmed}`;
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export function normalizeColors(colors: ResolvedColors): ResolvedColors {
|
||||
const out = {} as ResolvedColors;
|
||||
for (const key of COLOR_KEYS) out[key] = normalizeColor(colors[key]);
|
||||
return out;
|
||||
}
|
||||
|
||||
export type OpenTUIBorderStyle = "single" | "double" | "rounded" | "heavy";
|
||||
|
||||
const OPENTUI_BORDER_STYLES: readonly OpenTUIBorderStyle[] = [
|
||||
"single",
|
||||
"double",
|
||||
"rounded",
|
||||
"heavy",
|
||||
];
|
||||
|
||||
export function resolveBorderStyle(name: string): OpenTUIBorderStyle {
|
||||
return (OPENTUI_BORDER_STYLES as readonly string[]).includes(name)
|
||||
? (name as OpenTUIBorderStyle)
|
||||
: "rounded";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { CliRenderEvents, type Renderable } from "@opentui/core";
|
||||
import { useRenderer } from "@opentui/solid";
|
||||
import { createEffect, createSignal, onCleanup, type Accessor } from "solid-js";
|
||||
|
||||
export function isInside(node: Renderable | null, root: Renderable | null): boolean {
|
||||
if (!root) return false;
|
||||
for (let current = node; current; current = current.parent) {
|
||||
if (current === root) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True while the renderer's native focus is `target` or one of its
|
||||
* descendants. Mouse clicks move native focus on their own (the renderer
|
||||
* focuses the nearest focusable ancestor of what was clicked), so this
|
||||
* follows both keyboard and mouse focus changes.
|
||||
*/
|
||||
export function useFocusWithin(target: Accessor<Renderable | null | undefined>): Accessor<boolean> {
|
||||
const renderer = useRenderer();
|
||||
const [focused, setFocused] = createSignal(false);
|
||||
|
||||
const sync = () => setFocused(isInside(renderer.currentFocusedRenderable, target() ?? null));
|
||||
|
||||
renderer.on(CliRenderEvents.FOCUSED_RENDERABLE, sync);
|
||||
onCleanup(() => renderer.off(CliRenderEvents.FOCUSED_RENDERABLE, sync));
|
||||
createEffect(sync);
|
||||
|
||||
return focused;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export { createSimpleContext } from "./helper.tsx";
|
||||
export { notify, dismiss, notifications, type Notification, type NotificationKind, type NotifyOptions } from "./notifications.ts";
|
||||
export { helpOpen, toggleHelp } from "./help.ts";
|
||||
export { isInside, useFocusWithin } from "./focus.ts";
|
||||
|
||||
+67
-19
@@ -1,13 +1,17 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { parse as parseYAML } from "yaml";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
type ConfigYAML,
|
||||
type OpenTUIBorderStyle,
|
||||
mergeConfig,
|
||||
normalizeColors,
|
||||
resolveBorderStyle,
|
||||
layerConfigs,
|
||||
resolveConfig,
|
||||
ThemeConfigSchema,
|
||||
} from "./config.ts";
|
||||
import { buildPresets } from "./presets.ts";
|
||||
import { configPath, readYamlFile } from "./yaml.ts";
|
||||
import { notify } from "./context/notifications.ts";
|
||||
import { buildPresets, type Presets } from "./presets.ts";
|
||||
import { configPath } from "./yaml.ts";
|
||||
|
||||
export interface Theme {
|
||||
base00: string;
|
||||
@@ -47,12 +51,39 @@ export interface Theme {
|
||||
|
||||
export { configPath } from "./yaml.ts";
|
||||
|
||||
function buildTheme(): Theme {
|
||||
const defaultConfigPath = join(import.meta.dir, "default.yaml");
|
||||
const base = readYamlFile<ConfigYAML>(defaultConfigPath) ?? {};
|
||||
const user = readYamlFile<ConfigYAML>(configPath()) ?? {};
|
||||
const merged = mergeConfig(base, user);
|
||||
const colors = normalizeColors(merged.colors);
|
||||
function reportThemeError(path: string, message: string): void {
|
||||
const text = `${path}: ${message}`;
|
||||
console.error(`[ilovetui theme] ${text}`);
|
||||
notify(text, { kind: "warning", duration: 0 });
|
||||
}
|
||||
|
||||
// A broken file is reported (as a sticky warning toast, since console output
|
||||
// is hidden behind the TUI) and skipped as a whole, falling back to the
|
||||
// layers below it.
|
||||
function readThemeFile(path: string): ConfigYAML {
|
||||
if (!existsSync(path)) return {};
|
||||
let raw: unknown;
|
||||
try {
|
||||
raw = parseYAML(readFileSync(path, "utf8"));
|
||||
} catch (error) {
|
||||
reportThemeError(path, error instanceof Error ? error.message : String(error));
|
||||
return {};
|
||||
}
|
||||
const result = ThemeConfigSchema.safeParse(raw ?? {});
|
||||
if (!result.success) {
|
||||
reportThemeError(path, z.prettifyError(result.error));
|
||||
return {};
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
|
||||
// Read once: the shipped defaults, then the user's shared
|
||||
// ~/.config/ilovetui/config.yaml on top.
|
||||
const fileLayers: ConfigYAML[] = [readThemeFile(join(import.meta.dir, "default.yaml")), readThemeFile(configPath())];
|
||||
|
||||
function buildTheme(overrides: ConfigYAML): Theme {
|
||||
const resolved = resolveConfig(layerConfigs([...fileLayers, overrides]));
|
||||
const colors = resolved.colors;
|
||||
|
||||
return {
|
||||
...colors,
|
||||
@@ -68,17 +99,34 @@ function buildTheme(): Theme {
|
||||
warning: colors.base09,
|
||||
error: colors.base08,
|
||||
|
||||
nerdFonts: merged.nerdFonts,
|
||||
emojiFonts: merged.emojiFonts,
|
||||
reducedMotion: merged.reducedMotion,
|
||||
mouse: merged.mouse,
|
||||
borderStyle: resolveBorderStyle(merged.border),
|
||||
nerdFonts: resolved.nerdFonts,
|
||||
emojiFonts: resolved.emojiFonts,
|
||||
reducedMotion: resolved.reducedMotion,
|
||||
mouse: resolved.mouse,
|
||||
borderStyle: resolved.border,
|
||||
};
|
||||
}
|
||||
|
||||
export const theme: Theme = buildTheme();
|
||||
export const theme: Theme = buildTheme({});
|
||||
|
||||
export const presets = buildPresets(theme);
|
||||
export const presets: Presets = buildPresets(theme);
|
||||
|
||||
export type { OpenTUIBorderStyle } from "./config.ts";
|
||||
/**
|
||||
* Layers app-specific overrides (typically a `theme:` section of the app's own
|
||||
* config, validated with `ThemeConfigSchema`) on top of the shared ilovetui
|
||||
* config. `theme` and `presets` are updated in place, so call this once at
|
||||
* startup, before the first render.
|
||||
*/
|
||||
export function configureTheme(overrides: ConfigYAML = {}): void {
|
||||
Object.assign(theme, buildTheme(overrides));
|
||||
const next = buildPresets(theme);
|
||||
for (const key of Object.keys(next) as (keyof Presets)[]) Object.assign(presets[key], next[key]);
|
||||
}
|
||||
|
||||
/** A Nerd Font glyph followed by a space, or "" when `nerd_fonts` is off. */
|
||||
export function icon(codepoint: string): string {
|
||||
return theme.nerdFonts ? `${codepoint} ` : "";
|
||||
}
|
||||
|
||||
export { ThemeConfigSchema, type ConfigYAML as ThemeConfig, type OpenTUIBorderStyle } from "./config.ts";
|
||||
export type { Presets, SelectPreset, TabSelectPreset, InputPreset, BoxPreset } from "./presets.ts";
|
||||
|
||||
@@ -71,6 +71,66 @@ export function loadProjectConfig<TDefault, TUser>(
|
||||
return { defaults: defaultsResult.data, user };
|
||||
}
|
||||
|
||||
export interface MergedProjectConfigOptions<T> {
|
||||
projectName: string;
|
||||
defaultConfigPath: string;
|
||||
/** Schema for the complete config; the defaults file must satisfy it on its own. */
|
||||
schema: z.ZodType<T>;
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
// Objects merge key by key; anything else (scalars, arrays) from `over` replaces `base`.
|
||||
function deepMerge(base: unknown, over: unknown): unknown {
|
||||
if (!isPlainObject(base) || !isPlainObject(over)) return over === undefined ? base : over;
|
||||
const out: Record<string, unknown> = { ...base };
|
||||
for (const [key, value] of Object.entries(over)) out[key] = deepMerge(base[key], value);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single-schema alternative to loadProjectConfig(): the user's
|
||||
* ~/.config/<projectName>/config.yaml is deep-merged over the defaults file
|
||||
* and the result is validated once against `schema`, so the returned value is
|
||||
* fully typed and complete — no per-field `user.x ?? defaults.x` merging.
|
||||
* An unreadable or invalid user file is reported (warning toast + stderr) and
|
||||
* ignored as a whole; an invalid defaults file throws.
|
||||
*/
|
||||
export function loadMergedProjectConfig<T>(options: MergedProjectConfigOptions<T>): T {
|
||||
const { projectName, defaultConfigPath, schema } = options;
|
||||
|
||||
let rawDefaults: unknown;
|
||||
try {
|
||||
rawDefaults = readYaml(defaultConfigPath);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read ${defaultConfigPath}: ${error instanceof Error ? error.message : error}`);
|
||||
}
|
||||
const defaults = schema.safeParse(rawDefaults);
|
||||
if (!defaults.success) {
|
||||
throw new Error(`${defaultConfigPath} is invalid:\n${z.prettifyError(defaults.error)}`);
|
||||
}
|
||||
|
||||
const path = userConfigPath(projectName);
|
||||
if (!existsSync(path)) return defaults.data;
|
||||
|
||||
let rawUser: unknown;
|
||||
try {
|
||||
rawUser = readYaml(path);
|
||||
} catch (error) {
|
||||
warn(projectName, path, error instanceof Error ? error.message : String(error));
|
||||
return defaults.data;
|
||||
}
|
||||
|
||||
const merged = schema.safeParse(deepMerge(rawDefaults, rawUser ?? {}));
|
||||
if (!merged.success) {
|
||||
warn(projectName, path, z.prettifyError(merged.error));
|
||||
return defaults.data;
|
||||
}
|
||||
return merged.data;
|
||||
}
|
||||
|
||||
export function keybindsSchema<TName extends string>(
|
||||
names: readonly TName[],
|
||||
required: true,
|
||||
|
||||
+15
-11
@@ -21,31 +21,35 @@ type AnyCtor = new (...args: any[]) => any;
|
||||
|
||||
function withThemeDefaults<T extends AnyCtor>(
|
||||
Ctor: T,
|
||||
defaults: object,
|
||||
defaults: () => object,
|
||||
after?: (instance: any) => void,
|
||||
): T {
|
||||
class Themed extends Ctor {
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
Object.assign(this, defaults);
|
||||
Object.assign(this, defaults());
|
||||
after?.(this);
|
||||
}
|
||||
}
|
||||
return Themed as T;
|
||||
}
|
||||
|
||||
const { borderStyle, borderColor, focusedBorderColor } = presets.box;
|
||||
const boxDefaults = { borderStyle, borderColor, focusedBorderColor };
|
||||
// Defaults are read per instance (not captured at import) so that
|
||||
// configureTheme() overrides applied at startup are picked up.
|
||||
const boxDefaults = () => {
|
||||
const { borderStyle, borderColor, focusedBorderColor } = presets.box;
|
||||
return { borderStyle, borderColor, focusedBorderColor };
|
||||
};
|
||||
|
||||
extend({
|
||||
box: withThemeDefaults(BoxRenderable, boxDefaults, (instance) => {
|
||||
instance.border = false;
|
||||
}),
|
||||
select: withThemeDefaults(SelectRenderable, presets.select),
|
||||
tab_select: withThemeDefaults(TabSelectRenderable, presets.tabSelect),
|
||||
input: withThemeDefaults(InputRenderable, presets.input),
|
||||
textarea: withThemeDefaults(TextareaRenderable, presets.textarea),
|
||||
slider: withThemeDefaults(SliderRenderable, presets.slider),
|
||||
text: withThemeDefaults(TextRenderable, { fg: theme.text }),
|
||||
ascii_font: withThemeDefaults(ASCIIFontRenderable, { color: theme.primary }),
|
||||
select: withThemeDefaults(SelectRenderable, () => presets.select),
|
||||
tab_select: withThemeDefaults(TabSelectRenderable, () => presets.tabSelect),
|
||||
input: withThemeDefaults(InputRenderable, () => presets.input),
|
||||
textarea: withThemeDefaults(TextareaRenderable, () => presets.textarea),
|
||||
slider: withThemeDefaults(SliderRenderable, () => presets.slider),
|
||||
text: withThemeDefaults(TextRenderable, () => ({ fg: theme.text })),
|
||||
ascii_font: withThemeDefaults(ASCIIFontRenderable, () => ({ color: theme.primary })),
|
||||
});
|
||||
|
||||
-12
@@ -1,19 +1,7 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { parse as parseYAML } from "yaml";
|
||||
|
||||
export function configPath(): string {
|
||||
const configDir = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
||||
return join(configDir, "ilovetui", "config.yaml");
|
||||
}
|
||||
|
||||
export function readYamlFile<T>(path: string): T | undefined {
|
||||
if (!existsSync(path)) return undefined;
|
||||
try {
|
||||
return (parseYAML(readFileSync(path, "utf8")) as T) ?? undefined;
|
||||
} catch (err) {
|
||||
console.error(`ilovetui: failed to parse ${path}, ignoring it.`, err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user