tmux-ide 1.2.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +4 -216
- package/bin/cli.ts +536 -0
- package/package.json +33 -14
- package/src/__tests__/support.ts +186 -0
- package/src/{attach.js → attach.ts} +7 -4
- package/src/cli.test.ts +314 -0
- package/src/command-center/discovery.test.ts +265 -0
- package/src/command-center/discovery.ts +296 -0
- package/src/command-center/index.ts +31 -0
- package/src/command-center/schemas.ts +57 -0
- package/src/command-center/server.test.ts +818 -0
- package/src/command-center/server.ts +1426 -0
- package/src/config-cli.test.ts +86 -0
- package/src/config.test.ts +205 -0
- package/src/config.ts +313 -0
- package/src/detect.test.ts +142 -0
- package/src/{detect.js → detect.ts} +31 -15
- package/src/dispatch.ts +102 -0
- package/src/doctor.test.ts +119 -0
- package/src/doctor.ts +167 -0
- package/src/init.test.ts +101 -0
- package/src/init.ts +144 -0
- package/src/inspect.test.ts +67 -0
- package/src/{inspect.js → inspect.ts} +95 -10
- package/src/integration.test.ts.skip +276 -0
- package/src/js-yaml.d.ts +10 -0
- package/src/launch.test.ts +222 -0
- package/src/launch.ts +494 -0
- package/src/lib/auth/auth-service.test.ts +178 -0
- package/src/lib/auth/auth-service.ts +227 -0
- package/src/lib/auth/middleware.test.ts +119 -0
- package/src/lib/auth/middleware.ts +56 -0
- package/src/lib/auth/types.ts +13 -0
- package/src/lib/authorship.test.ts +242 -0
- package/src/lib/authorship.ts +280 -0
- package/src/lib/cast/recorder.test.ts +127 -0
- package/src/lib/cast/recorder.ts +148 -0
- package/src/lib/daemon-watchdog.ts +102 -0
- package/src/lib/daemon.ts +417 -0
- package/src/lib/dot-path.test.ts +66 -0
- package/src/lib/dot-path.ts +17 -0
- package/src/lib/errors.ts +44 -0
- package/src/lib/event-log.test.ts +189 -0
- package/src/lib/event-log.ts +237 -0
- package/src/lib/github-pr.ts +182 -0
- package/src/lib/hq/client.test.ts +154 -0
- package/src/lib/hq/client.ts +142 -0
- package/src/lib/hq/mdns.test.ts +54 -0
- package/src/lib/hq/mdns.ts +88 -0
- package/src/lib/hq/registry.test.ts +237 -0
- package/src/lib/hq/registry.ts +145 -0
- package/src/lib/hq/types.ts +44 -0
- package/src/lib/ipc/socket-protocol.test.ts +126 -0
- package/src/lib/ipc/socket-protocol.ts +324 -0
- package/src/lib/launch-plan.test.ts +119 -0
- package/src/lib/launch-plan.ts +90 -0
- package/src/lib/log.test.ts +39 -0
- package/src/lib/log.ts +71 -0
- package/src/lib/metrics.test.ts +213 -0
- package/src/lib/metrics.ts +345 -0
- package/src/lib/orchestrator.test.ts +2358 -0
- package/src/lib/orchestrator.ts +1792 -0
- package/src/lib/{output.js → output.ts} +18 -10
- package/src/lib/plan-store.test.ts +148 -0
- package/src/lib/plan-store.ts +131 -0
- package/src/lib/research.test.ts +208 -0
- package/src/lib/research.ts +475 -0
- package/src/lib/round-trip.test.ts +341 -0
- package/src/lib/session-monitor.test.ts +152 -0
- package/src/lib/session-monitor.ts +282 -0
- package/src/lib/session-options.test.ts +146 -0
- package/src/lib/{session-options.js → session-options.ts} +11 -6
- package/src/lib/shell.test.ts +20 -0
- package/src/lib/shell.ts +8 -0
- package/src/lib/sizes.test.ts +73 -0
- package/src/lib/{sizes.js → sizes.ts} +4 -4
- package/src/lib/skill-registry.test.ts +134 -0
- package/src/lib/skill-registry.ts +86 -0
- package/src/lib/slugify.test.ts +35 -0
- package/src/lib/slugify.ts +10 -0
- package/src/lib/task-store.ts +352 -0
- package/src/lib/tmux.test.ts +500 -0
- package/src/lib/tmux.ts +372 -0
- package/src/lib/token-tracker.test.ts +102 -0
- package/src/lib/token-tracker.ts +70 -0
- package/src/lib/tunnels/cloudflare.test.ts +73 -0
- package/src/lib/tunnels/cloudflare.ts +254 -0
- package/src/lib/tunnels/manager.test.ts +63 -0
- package/src/lib/tunnels/manager.ts +132 -0
- package/src/lib/tunnels/ngrok.test.ts +94 -0
- package/src/lib/tunnels/ngrok.ts +261 -0
- package/src/lib/tunnels/tailscale.test.ts +103 -0
- package/src/lib/tunnels/tailscale.ts +1048 -0
- package/src/lib/tunnels/types.ts +44 -0
- package/src/lib/validation.test.ts +289 -0
- package/src/lib/validation.ts +123 -0
- package/src/lib/webhook.ts +51 -0
- package/src/lib/workflow-store.ts +209 -0
- package/src/lib/ws-v3/protocol.test.ts +127 -0
- package/src/lib/ws-v3/protocol.ts +160 -0
- package/src/lib/yaml-io.test.ts +125 -0
- package/src/lib/{yaml-io.js → yaml-io.ts} +5 -4
- package/src/ls.test.ts +76 -0
- package/src/{ls.js → ls.ts} +4 -4
- package/src/metrics-cli.ts +177 -0
- package/src/notify.ts +85 -0
- package/src/orchestrator-status.ts +228 -0
- package/src/plan.ts +117 -0
- package/src/postinstall.test.ts +79 -0
- package/src/remote.test.ts +115 -0
- package/src/remote.ts +148 -0
- package/src/restart.ts +24 -0
- package/src/schemas/domain.ts +422 -0
- package/src/schemas/ide-config.ts +148 -0
- package/src/schemas/index.ts +53 -0
- package/src/send.test.ts +70 -0
- package/src/send.ts +181 -0
- package/src/skill.test.ts +114 -0
- package/src/skill.ts +138 -0
- package/src/status.ts +66 -0
- package/src/stop.test.ts +176 -0
- package/src/stop.ts +47 -0
- package/src/task.test.ts +1466 -0
- package/src/task.ts +1143 -0
- package/src/tunnel.test.ts +80 -0
- package/src/tunnel.ts +133 -0
- package/src/types.ts +15 -0
- package/src/ui/index.ts +32 -0
- package/src/ui/terminal/index.ts +9 -0
- package/src/ui/types.ts +91 -0
- package/src/ui/web/base.css +80 -0
- package/src/ui/web/components/Box.tsx +59 -0
- package/src/ui/web/components/Input.tsx +32 -0
- package/src/ui/web/components/ScrollBox.tsx +60 -0
- package/src/ui/web/components/Text.tsx +28 -0
- package/src/ui/web/hooks.ts +106 -0
- package/src/ui/web/index.ts +27 -0
- package/src/ui/web/render.ts +77 -0
- package/src/ui/web/utils/color.test.ts +93 -0
- package/src/ui/web/utils/color.ts +27 -0
- package/src/validate.test.ts +385 -0
- package/src/validate.ts +217 -0
- package/src/widgets/changes/index.tsx +576 -0
- package/src/widgets/config/index.tsx +440 -0
- package/src/widgets/costs/index.tsx +216 -0
- package/src/widgets/explorer/breadcrumbs.tsx +77 -0
- package/src/widgets/explorer/footer.tsx +20 -0
- package/src/widgets/explorer/header.tsx +23 -0
- package/src/widgets/explorer/index.tsx +408 -0
- package/src/widgets/explorer/tree-model.test.ts +214 -0
- package/src/widgets/explorer/tree-model.ts +103 -0
- package/src/widgets/explorer/tree.tsx +156 -0
- package/src/widgets/lib/config-model.ts +116 -0
- package/src/widgets/lib/files.test.ts +103 -0
- package/src/widgets/lib/files.ts +88 -0
- package/src/widgets/lib/git.test.ts +151 -0
- package/src/widgets/lib/git.ts +88 -0
- package/src/widgets/lib/pane-comms.test.ts +178 -0
- package/src/widgets/lib/pane-comms.ts +192 -0
- package/src/widgets/lib/theme.ts +151 -0
- package/src/widgets/lib/watcher.ts +50 -0
- package/src/widgets/mission-control/activity-feed.tsx +59 -0
- package/src/widgets/mission-control/agent-panel.tsx +132 -0
- package/src/widgets/mission-control/command-bar.tsx +63 -0
- package/src/widgets/mission-control/index.tsx +751 -0
- package/src/widgets/mission-control/task-panel.tsx +238 -0
- package/src/widgets/preview/index.tsx +415 -0
- package/src/widgets/resolve.ts +40 -0
- package/src/widgets/setup/agent-naming.tsx +112 -0
- package/src/widgets/setup/config-tree.tsx +238 -0
- package/src/widgets/setup/detect-panel.tsx +72 -0
- package/src/widgets/setup/field-editor.tsx +265 -0
- package/src/widgets/setup/footer.tsx +107 -0
- package/src/widgets/setup/index.tsx +340 -0
- package/src/widgets/setup/layout-picker.tsx +96 -0
- package/src/widgets/setup/orchestrator-panel.tsx +200 -0
- package/src/widgets/setup/review-panel.tsx +140 -0
- package/src/widgets/setup/setup-model.test.ts +303 -0
- package/src/widgets/setup/setup-model.ts +188 -0
- package/src/widgets/tasks/index.tsx +129 -0
- package/src/widgets/tasks/task-detail.tsx +309 -0
- package/src/widgets/tasks/task-form.tsx +193 -0
- package/src/widgets/tasks/task-list.tsx +205 -0
- package/src/widgets/tasks/task-model.test.ts +473 -0
- package/src/widgets/tasks/task-model.ts +157 -0
- package/templates/AGENTS.md +20 -0
- package/templates/missions.yml +41 -0
- package/templates/skills/backend.md +30 -0
- package/templates/skills/frontend.md +30 -0
- package/templates/skills/general-worker.md +30 -0
- package/templates/skills/researcher.md +69 -0
- package/templates/skills/reviewer.md +30 -0
- package/src/config.js +0 -341
- package/src/doctor.js +0 -91
- package/src/init.js +0 -73
- package/src/launch.js +0 -245
- package/src/lib/dot-path.js +0 -16
- package/src/lib/errors.js +0 -35
- package/src/lib/launch-plan.js +0 -49
- package/src/lib/session-monitor.js +0 -179
- package/src/lib/tmux.js +0 -237
- package/src/restart.js +0 -16
- package/src/status.js +0 -35
- package/src/stop.js +0 -25
- package/src/validate.js +0 -154
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { createSignal, onMount, onCleanup } from "solid-js";
|
|
2
|
+
|
|
3
|
+
/** Synthetic key event passed to handlers (mirrors OpenTUI naming). */
|
|
4
|
+
export interface UiKeyEvent {
|
|
5
|
+
name: string;
|
|
6
|
+
ctrl: boolean;
|
|
7
|
+
shift: boolean;
|
|
8
|
+
alt: boolean;
|
|
9
|
+
meta: boolean;
|
|
10
|
+
preventDefault: () => void;
|
|
11
|
+
stopPropagation: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useKeyboard(handler: (evt: UiKeyEvent) => void): void {
|
|
15
|
+
onMount(() => {
|
|
16
|
+
const listener = (e: KeyboardEvent) => {
|
|
17
|
+
const evt = {
|
|
18
|
+
name: mapKeyName(e),
|
|
19
|
+
ctrl: e.ctrlKey,
|
|
20
|
+
shift: e.shiftKey,
|
|
21
|
+
alt: e.altKey,
|
|
22
|
+
meta: e.metaKey,
|
|
23
|
+
preventDefault: () => e.preventDefault(),
|
|
24
|
+
stopPropagation: () => e.stopPropagation(),
|
|
25
|
+
};
|
|
26
|
+
handler(evt);
|
|
27
|
+
};
|
|
28
|
+
document.addEventListener("keydown", listener);
|
|
29
|
+
onCleanup(() => document.removeEventListener("keydown", listener));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function mapKeyName(e: KeyboardEvent): string {
|
|
34
|
+
switch (e.key) {
|
|
35
|
+
case "ArrowUp":
|
|
36
|
+
return "up";
|
|
37
|
+
case "ArrowDown":
|
|
38
|
+
return "down";
|
|
39
|
+
case "ArrowLeft":
|
|
40
|
+
return "left";
|
|
41
|
+
case "ArrowRight":
|
|
42
|
+
return "right";
|
|
43
|
+
case "Enter":
|
|
44
|
+
return "return";
|
|
45
|
+
case "Escape":
|
|
46
|
+
return "escape";
|
|
47
|
+
case "Backspace":
|
|
48
|
+
return "backspace";
|
|
49
|
+
case "Tab":
|
|
50
|
+
return "tab";
|
|
51
|
+
case "Delete":
|
|
52
|
+
return "delete";
|
|
53
|
+
case " ":
|
|
54
|
+
return "space";
|
|
55
|
+
case "Home":
|
|
56
|
+
return "home";
|
|
57
|
+
case "End":
|
|
58
|
+
return "end";
|
|
59
|
+
case "PageUp":
|
|
60
|
+
return "pageup";
|
|
61
|
+
case "PageDown":
|
|
62
|
+
return "pagedown";
|
|
63
|
+
case "Insert":
|
|
64
|
+
return "insert";
|
|
65
|
+
default:
|
|
66
|
+
// @opentui convention: single-char keys are always lowercase, with shift as a modifier.
|
|
67
|
+
// Browser gives uppercase e.key for Shift+letter (e.g. "G"), so normalize to lowercase.
|
|
68
|
+
if (e.key.length === 1) return e.key.toLowerCase();
|
|
69
|
+
return e.key;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function useTerminalDimensions() {
|
|
74
|
+
const [dims, setDims] = createSignal({ width: 80, height: 24 });
|
|
75
|
+
|
|
76
|
+
onMount(() => {
|
|
77
|
+
const update = () => {
|
|
78
|
+
// Measure a ch unit by using a temporary element
|
|
79
|
+
const measure = document.createElement("span");
|
|
80
|
+
measure.style.position = "absolute";
|
|
81
|
+
measure.style.visibility = "hidden";
|
|
82
|
+
measure.style.fontFamily = "var(--font-family, 'IBM Plex Mono', ui-monospace, monospace)";
|
|
83
|
+
measure.style.fontSize = "var(--font-size, 13px)";
|
|
84
|
+
measure.textContent = "0";
|
|
85
|
+
document.body.appendChild(measure);
|
|
86
|
+
const chWidth = measure.getBoundingClientRect().width;
|
|
87
|
+
document.body.removeChild(measure);
|
|
88
|
+
|
|
89
|
+
const lh = parseFloat(getComputedStyle(document.documentElement).lineHeight);
|
|
90
|
+
|
|
91
|
+
const effectiveCh = chWidth > 0 ? chWidth : 7.8; // fallback
|
|
92
|
+
const effectiveLh = lh > 0 ? lh : 18.2; // fallback
|
|
93
|
+
|
|
94
|
+
setDims({
|
|
95
|
+
width: Math.floor(window.innerWidth / effectiveCh),
|
|
96
|
+
height: Math.floor(window.innerHeight / effectiveLh),
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
update();
|
|
101
|
+
window.addEventListener("resize", update);
|
|
102
|
+
onCleanup(() => window.removeEventListener("resize", update));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return dims;
|
|
106
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Web mirror components — lowercase exports match @opentui JSX element names
|
|
2
|
+
export { Box as box } from "./components/Box.tsx";
|
|
3
|
+
export { Text as text } from "./components/Text.tsx";
|
|
4
|
+
export { ScrollBox as scrollbox } from "./components/ScrollBox.tsx";
|
|
5
|
+
export { Input as input } from "./components/Input.tsx";
|
|
6
|
+
|
|
7
|
+
// Render and hooks
|
|
8
|
+
export { render } from "./render.ts";
|
|
9
|
+
export { useKeyboard, useTerminalDimensions } from "./hooks.ts";
|
|
10
|
+
|
|
11
|
+
// Re-export Solid.js primitives
|
|
12
|
+
export {
|
|
13
|
+
Show,
|
|
14
|
+
For,
|
|
15
|
+
Switch,
|
|
16
|
+
Match,
|
|
17
|
+
createSignal,
|
|
18
|
+
createMemo,
|
|
19
|
+
createEffect,
|
|
20
|
+
onMount,
|
|
21
|
+
onCleanup,
|
|
22
|
+
batch,
|
|
23
|
+
} from "solid-js";
|
|
24
|
+
|
|
25
|
+
// Re-export shared types
|
|
26
|
+
export { RGBA, TextAttributes } from "../types.ts";
|
|
27
|
+
export type { BoxProps, TextProps, ScrollBoxProps, InputProps } from "../types.ts";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { JSX } from "solid-js";
|
|
2
|
+
import { render as solidRender } from "solid-js/web";
|
|
3
|
+
|
|
4
|
+
const BASE_CSS = `
|
|
5
|
+
:root {
|
|
6
|
+
--line-height: 1.4em;
|
|
7
|
+
--font-family: 'IBM Plex Mono', ui-monospace, monospace;
|
|
8
|
+
--font-size: 13px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
body, #root {
|
|
12
|
+
font-family: var(--font-family);
|
|
13
|
+
font-size: var(--font-size);
|
|
14
|
+
line-height: var(--line-height);
|
|
15
|
+
background: rgb(19, 16, 16);
|
|
16
|
+
color: rgb(232, 228, 228);
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: 0;
|
|
19
|
+
-webkit-font-smoothing: antialiased;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
* {
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
div {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
min-width: 0;
|
|
30
|
+
min-height: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
span {
|
|
34
|
+
font-family: inherit;
|
|
35
|
+
font-size: inherit;
|
|
36
|
+
line-height: inherit;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
input {
|
|
40
|
+
font-family: inherit;
|
|
41
|
+
font-size: inherit;
|
|
42
|
+
line-height: inherit;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
::-webkit-scrollbar {
|
|
46
|
+
width: 8px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
::-webkit-scrollbar-track {
|
|
50
|
+
background: rgb(25, 25, 35);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
::-webkit-scrollbar-thumb {
|
|
54
|
+
background: rgb(60, 60, 80);
|
|
55
|
+
border-radius: 4px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
::-webkit-scrollbar-thumb:hover {
|
|
59
|
+
background: rgb(80, 80, 100);
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
let stylesInjected = false;
|
|
64
|
+
|
|
65
|
+
function injectBaseStyles(): void {
|
|
66
|
+
if (stylesInjected) return;
|
|
67
|
+
const style = document.createElement("style");
|
|
68
|
+
style.textContent = BASE_CSS;
|
|
69
|
+
document.head.appendChild(style);
|
|
70
|
+
stylesInjected = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function render(component: () => JSX.Element, _options?: unknown): void {
|
|
74
|
+
injectBaseStyles();
|
|
75
|
+
const root = document.getElementById("root") ?? document.body;
|
|
76
|
+
solidRender(component, root);
|
|
77
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { rgbaToCSS, attributesToStyle } from "./color.ts";
|
|
3
|
+
import { rgbaFromInts, TextAttributes } from "../../types.ts";
|
|
4
|
+
|
|
5
|
+
describe("rgbaToCSS", () => {
|
|
6
|
+
it("converts opaque color to rgb()", () => {
|
|
7
|
+
expect(rgbaToCSS({ r: 255, g: 128, b: 0, a: 255 })).toBe("rgb(255,128,0)");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("converts transparent (a=0) to 'transparent'", () => {
|
|
11
|
+
expect(rgbaToCSS({ r: 100, g: 100, b: 100, a: 0 })).toBe("transparent");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("converts semi-transparent color to rgba()", () => {
|
|
15
|
+
expect(rgbaToCSS({ r: 10, g: 20, b: 30, a: 128 })).toBe("rgba(10,20,30,0.50)");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("handles rgbaFromInts()", () => {
|
|
19
|
+
const color = rgbaFromInts(75, 0, 130);
|
|
20
|
+
expect(rgbaToCSS(color)).toBe("rgb(75,0,130)");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("rgbaFromInts defaults a to 255", () => {
|
|
24
|
+
const color = rgbaFromInts(1, 2, 3);
|
|
25
|
+
expect(color.a).toBe(255);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("rgbaFromInts accepts explicit alpha", () => {
|
|
29
|
+
const color = rgbaFromInts(1, 2, 3, 100);
|
|
30
|
+
expect(color.a).toBe(100);
|
|
31
|
+
expect(rgbaToCSS(color)).toBe("rgba(1,2,3,0.39)");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("attributesToStyle", () => {
|
|
36
|
+
it("returns empty object for NONE", () => {
|
|
37
|
+
expect(attributesToStyle(TextAttributes.NONE)).toEqual({});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("maps BOLD to font-weight", () => {
|
|
41
|
+
const style = attributesToStyle(TextAttributes.BOLD);
|
|
42
|
+
expect(style["font-weight"]).toBe("bold");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("maps DIM to opacity", () => {
|
|
46
|
+
const style = attributesToStyle(TextAttributes.DIM);
|
|
47
|
+
expect(style.opacity).toBe("0.6");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("maps ITALIC to font-style", () => {
|
|
51
|
+
const style = attributesToStyle(TextAttributes.ITALIC);
|
|
52
|
+
expect(style["font-style"]).toBe("italic");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("maps UNDERLINE to text-decoration", () => {
|
|
56
|
+
const style = attributesToStyle(TextAttributes.UNDERLINE);
|
|
57
|
+
expect(style["text-decoration"]).toBe("underline");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("maps STRIKETHROUGH to text-decoration", () => {
|
|
61
|
+
const style = attributesToStyle(TextAttributes.STRIKETHROUGH);
|
|
62
|
+
expect(style["text-decoration"]).toBe("line-through");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("composes UNDERLINE + STRIKETHROUGH", () => {
|
|
66
|
+
const style = attributesToStyle(TextAttributes.UNDERLINE | TextAttributes.STRIKETHROUGH);
|
|
67
|
+
expect(style["text-decoration"]).toBe("underline line-through");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("maps BLINK to animation", () => {
|
|
71
|
+
const style = attributesToStyle(TextAttributes.BLINK);
|
|
72
|
+
expect(style.animation?.includes("blink")).toBeTruthy();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("maps INVERSE to filter invert", () => {
|
|
76
|
+
const style = attributesToStyle(TextAttributes.INVERSE);
|
|
77
|
+
expect(style.filter).toBe("invert(1)");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("maps HIDDEN to visibility hidden", () => {
|
|
81
|
+
const style = attributesToStyle(TextAttributes.HIDDEN);
|
|
82
|
+
expect(style.visibility).toBe("hidden");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("composes multiple attributes", () => {
|
|
86
|
+
const style = attributesToStyle(
|
|
87
|
+
TextAttributes.BOLD | TextAttributes.ITALIC | TextAttributes.UNDERLINE,
|
|
88
|
+
);
|
|
89
|
+
expect(style["font-weight"]).toBe("bold");
|
|
90
|
+
expect(style["font-style"]).toBe("italic");
|
|
91
|
+
expect(style["text-decoration"]).toBe("underline");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RGBA } from "../../types.ts";
|
|
2
|
+
|
|
3
|
+
export function rgbaToCSS(color: RGBA): string {
|
|
4
|
+
if (color.a === 0) return "transparent";
|
|
5
|
+
if (color.a === 255 || color.a === undefined) {
|
|
6
|
+
return `rgb(${color.r},${color.g},${color.b})`;
|
|
7
|
+
}
|
|
8
|
+
return `rgba(${color.r},${color.g},${color.b},${(color.a / 255).toFixed(2)})`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function attributesToStyle(attrs: number): Record<string, string> {
|
|
12
|
+
const style: Record<string, string> = {};
|
|
13
|
+
if (attrs & 1) style["font-weight"] = "bold"; // BOLD
|
|
14
|
+
if (attrs & 2) style.opacity = "0.6"; // DIM
|
|
15
|
+
if (attrs & 4) style["font-style"] = "italic"; // ITALIC
|
|
16
|
+
|
|
17
|
+
// Compose text-decoration: underline and line-through can coexist
|
|
18
|
+
const decorations: string[] = [];
|
|
19
|
+
if (attrs & 8) decorations.push("underline"); // UNDERLINE
|
|
20
|
+
if (attrs & 128) decorations.push("line-through"); // STRIKETHROUGH
|
|
21
|
+
if (decorations.length > 0) style["text-decoration"] = decorations.join(" ");
|
|
22
|
+
|
|
23
|
+
if (attrs & 16) style.animation = "blink 1s step-end infinite"; // BLINK
|
|
24
|
+
if (attrs & 32) style.filter = "invert(1)"; // INVERSE
|
|
25
|
+
if (attrs & 64) style.visibility = "hidden"; // HIDDEN
|
|
26
|
+
return style;
|
|
27
|
+
}
|