@opencode-ai/theme 0.0.0-next-16413
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tui/color.d.ts +8 -0
- package/dist/tui/color.js +67 -0
- package/dist/tui/defaults.d.ts +552 -0
- package/dist/tui/defaults.js +437 -0
- package/dist/tui/expand.d.ts +4 -0
- package/dist/tui/expand.js +87 -0
- package/dist/tui/fallback.d.ts +2 -0
- package/dist/tui/fallback.js +54 -0
- package/dist/tui/index.d.ts +7 -0
- package/dist/tui/index.js +6 -0
- package/dist/tui/resolve.d.ts +5 -0
- package/dist/tui/resolve.js +214 -0
- package/dist/tui/schema.d.ts +5552 -0
- package/dist/tui/schema.js +182 -0
- package/dist/tui/select.d.ts +13 -0
- package/dist/tui/select.js +36 -0
- package/dist/tui/syntax.d.ts +3 -0
- package/dist/tui/syntax.js +75 -0
- package/dist/tui/types.d.ts +77 -0
- package/dist/tui/types.js +1 -0
- package/dist/tui/v1-migrate.d.ts +3 -0
- package/dist/tui/v1-migrate.js +429 -0
- package/dist/tui/v1.d.ts +74 -0
- package/dist/tui/v1.js +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { RGBA } from "@opentui/core";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
import { DEFAULT_CATEGORICAL, DEFAULT_THEME } from "./defaults.js";
|
|
4
|
+
import { expandTheme, expandTokens, mergeTheme } from "./expand.js";
|
|
5
|
+
import { fallback } from "./fallback.js";
|
|
6
|
+
import { ActionState, ActionVariant, BaseHue, FeedbackKind, HueAlias, HueStep, ThemeDefinition, ThemeDocument, } from "./schema.js";
|
|
7
|
+
import { selectTheme, selectThemeMode } from "./select.js";
|
|
8
|
+
const decodeThemeDefinitionSchema = Schema.decodeUnknownSync(ThemeDefinition);
|
|
9
|
+
function decodeThemeDefinition(input) {
|
|
10
|
+
try {
|
|
11
|
+
return decodeThemeDefinitionSchema(input);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
throw themeDecodeError(error, "theme");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function themeDecodeError(error, name) {
|
|
18
|
+
const message = Schema.isSchemaError(error) ? error.message : String(error);
|
|
19
|
+
const value = /got ("[^"]*"|\S+)/.exec(message)?.[1] ?? "value";
|
|
20
|
+
return new Error(`Invalid theme: ${name} ${value} is an invalid value`, { cause: error });
|
|
21
|
+
}
|
|
22
|
+
export function resolveThemeDocument(document, mode) {
|
|
23
|
+
const selected = selectThemeMode(document, mode);
|
|
24
|
+
const definition = selected.expanded ? selected.theme : expandTheme(selected.theme);
|
|
25
|
+
const defaults = expandTheme(selectTheme(DEFAULT_THEME, selected.mode));
|
|
26
|
+
const core = expandTokens(fallback());
|
|
27
|
+
const merged = document.standalone ? mergeTheme(core, definition) : mergeTheme(core, defaults, definition);
|
|
28
|
+
if (!merged["hue"])
|
|
29
|
+
throw new Error("Standalone themes must provide hues");
|
|
30
|
+
return resolveExpandedTheme({
|
|
31
|
+
...merged,
|
|
32
|
+
categorical: merged["categorical"] ?? DEFAULT_CATEGORICAL,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function resolveTheme(definition) {
|
|
36
|
+
return resolveExpandedTheme(expandTheme(decodeThemeDefinition(definition)));
|
|
37
|
+
}
|
|
38
|
+
function resolveExpandedTheme(definition) {
|
|
39
|
+
const hue = resolveHue(definition.hue);
|
|
40
|
+
const categorical = (definition.categorical ?? DEFAULT_CATEGORICAL).map((name) => hue[name]);
|
|
41
|
+
const hueSteps = compileHueSteps(hue);
|
|
42
|
+
const base = tokens(definition);
|
|
43
|
+
const resolved = resolveView(base, hue, categorical, hueSteps);
|
|
44
|
+
const contexts = Object.fromEntries(Object.entries(definition)
|
|
45
|
+
.filter(([key]) => key.startsWith("@context:"))
|
|
46
|
+
.map(([key, override]) => {
|
|
47
|
+
const contextual = contextualize(base, override);
|
|
48
|
+
return [key, resolveView(contextual, hue, categorical, hueSteps)];
|
|
49
|
+
}));
|
|
50
|
+
return { ...resolved, contexts };
|
|
51
|
+
}
|
|
52
|
+
function tokens(definition) {
|
|
53
|
+
return {
|
|
54
|
+
text: definition.text,
|
|
55
|
+
background: definition.background,
|
|
56
|
+
border: definition.border,
|
|
57
|
+
scrollbar: definition.scrollbar,
|
|
58
|
+
diff: definition.diff,
|
|
59
|
+
syntax: definition.syntax,
|
|
60
|
+
markdown: definition.markdown,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function contextualize(base, override) {
|
|
64
|
+
const result = mergeTheme(base, override);
|
|
65
|
+
const baseText = base.text?.action;
|
|
66
|
+
const contextText = override.text?.action;
|
|
67
|
+
const baseBackground = base.background?.action;
|
|
68
|
+
const contextBackground = override.background?.action;
|
|
69
|
+
const text = result["text"];
|
|
70
|
+
const background = result["background"];
|
|
71
|
+
return {
|
|
72
|
+
...result,
|
|
73
|
+
text: { ...text, action: contextualActions(baseText, contextText) },
|
|
74
|
+
background: { ...background, action: contextualActions(baseBackground, contextBackground) },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function contextualActions(base, context) {
|
|
78
|
+
return Object.fromEntries(ActionVariant.literals.map((variant) => {
|
|
79
|
+
const baseVariant = base?.[variant];
|
|
80
|
+
const contextVariant = context?.[variant];
|
|
81
|
+
return [
|
|
82
|
+
variant,
|
|
83
|
+
Object.fromEntries(["default", ...ActionState.literals].map((state) => {
|
|
84
|
+
const key = state === "default" ? undefined : `$${state}`;
|
|
85
|
+
return [
|
|
86
|
+
key ?? "default",
|
|
87
|
+
(key ? contextVariant?.[key] : undefined) ??
|
|
88
|
+
contextVariant?.default ??
|
|
89
|
+
(key ? baseVariant?.[key] : undefined) ??
|
|
90
|
+
baseVariant?.default,
|
|
91
|
+
];
|
|
92
|
+
})),
|
|
93
|
+
];
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
function resolveView(definition, hue, categorical, hueSteps) {
|
|
97
|
+
const source = { hue, ...definition };
|
|
98
|
+
return { ...createResolver(source)(source, "theme"), hue, categorical, ...hueSteps };
|
|
99
|
+
}
|
|
100
|
+
function compileHueSteps(hue) {
|
|
101
|
+
const index = new WeakMap();
|
|
102
|
+
for (const [name, scale] of Object.entries(hue)) {
|
|
103
|
+
HueStep.literals.forEach((step, position) => index.set(scale[step], { hue: name, step, position }));
|
|
104
|
+
}
|
|
105
|
+
const shift = (color, amount) => {
|
|
106
|
+
const match = index.get(color);
|
|
107
|
+
if (!match)
|
|
108
|
+
return color;
|
|
109
|
+
const offset = Number.isFinite(amount) ? Math.trunc(amount) : 0;
|
|
110
|
+
const position = Math.max(0, Math.min(HueStep.literals.length - 1, match.position + offset));
|
|
111
|
+
return hue[match.hue][HueStep.literals[position]];
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
source: (color) => {
|
|
115
|
+
const match = index.get(color);
|
|
116
|
+
return match ? { hue: match.hue, step: match.step } : undefined;
|
|
117
|
+
},
|
|
118
|
+
increase: (color, amount = 1) => shift(color, amount),
|
|
119
|
+
decrease: (color, amount = 1) => shift(color, -amount),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function resolveHue(definition) {
|
|
123
|
+
const source = definition;
|
|
124
|
+
const cache = new Map();
|
|
125
|
+
const expected = new Set([...BaseHue.literals, ...HueAlias.literals]);
|
|
126
|
+
for (const name of Object.keys(source)) {
|
|
127
|
+
if (!expected.has(name))
|
|
128
|
+
throw new Error(`Unknown hue "${name}"`);
|
|
129
|
+
}
|
|
130
|
+
function resolve(name, stack) {
|
|
131
|
+
const hit = cache.get(name);
|
|
132
|
+
if (hit)
|
|
133
|
+
return hit;
|
|
134
|
+
if (stack.includes(name))
|
|
135
|
+
throw new Error(`Circular hue reference: ${[...stack, name].join(" -> ")}`);
|
|
136
|
+
const value = source[name];
|
|
137
|
+
if (typeof value === "string") {
|
|
138
|
+
const match = /^\$hue\.([^.]+)$/.exec(value);
|
|
139
|
+
if (!match?.[1])
|
|
140
|
+
throw new Error(`Hue alias "${value}" must reference a hue scale`);
|
|
141
|
+
const target = resolve(match[1], [...stack, name]);
|
|
142
|
+
const result = Object.fromEntries(HueStep.literals.map((step) => [step, RGBA.clone(target[step])]));
|
|
143
|
+
cache.set(name, result);
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
if (!isRecord(value))
|
|
147
|
+
throw new Error(`Hue "${name}" was not found`);
|
|
148
|
+
const result = Object.fromEntries(HueStep.literals.map((step) => {
|
|
149
|
+
const color = value[step];
|
|
150
|
+
if (typeof color !== "string" || !isHex(color))
|
|
151
|
+
throw new Error(`Invalid hue color at "hue.${name}.${step}"`);
|
|
152
|
+
return [step, RGBA.fromHex(color)];
|
|
153
|
+
}));
|
|
154
|
+
for (const step of Object.keys(value)) {
|
|
155
|
+
if (!HueStep.literals.includes(Number(step)))
|
|
156
|
+
throw new Error(`Unknown hue step at "hue.${name}.${step}"`);
|
|
157
|
+
}
|
|
158
|
+
cache.set(name, result);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
return Object.fromEntries([...BaseHue.literals, ...HueAlias.literals].map((name) => [name, resolve(name, [])]));
|
|
162
|
+
}
|
|
163
|
+
function createResolver(source) {
|
|
164
|
+
const cache = new Map();
|
|
165
|
+
function resolve(value, path, stack = []) {
|
|
166
|
+
if (value instanceof RGBA)
|
|
167
|
+
return value;
|
|
168
|
+
if (typeof value === "string")
|
|
169
|
+
return resolveColor(value, path, stack);
|
|
170
|
+
if (typeof value === "number")
|
|
171
|
+
return value;
|
|
172
|
+
if (!isRecord(value))
|
|
173
|
+
throw new Error(`Invalid theme value at "${path}"`);
|
|
174
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [resolvedKey(key), resolve(item, `${path}.${key}`, stack)]));
|
|
175
|
+
}
|
|
176
|
+
function resolveColor(value, path, stack) {
|
|
177
|
+
if (value === "transparent")
|
|
178
|
+
return RGBA.fromInts(0, 0, 0, 0);
|
|
179
|
+
if (isHex(value))
|
|
180
|
+
return RGBA.fromHex(value);
|
|
181
|
+
if (!value.startsWith("$"))
|
|
182
|
+
throw new Error(`Invalid color "${value}" at "${path}"`);
|
|
183
|
+
const target = value.slice(1);
|
|
184
|
+
const hit = cache.get(target);
|
|
185
|
+
if (hit)
|
|
186
|
+
return hit;
|
|
187
|
+
if (stack.includes(target))
|
|
188
|
+
throw new Error(`Circular theme reference: ${[...stack, target].join(" -> ")}`);
|
|
189
|
+
const result = resolve(read(source, target), target, [...stack, target]);
|
|
190
|
+
if (!(result instanceof RGBA))
|
|
191
|
+
throw new Error(`Theme reference "${value}" at "${path}" is not a color`);
|
|
192
|
+
cache.set(target, result);
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
return (value, path) => resolve(value, path);
|
|
196
|
+
}
|
|
197
|
+
function resolvedKey(key) {
|
|
198
|
+
if (!key.startsWith("$"))
|
|
199
|
+
return key;
|
|
200
|
+
const state = key.slice(1);
|
|
201
|
+
return ActionState.literals.includes(state) ? state : key;
|
|
202
|
+
}
|
|
203
|
+
function read(source, path) {
|
|
204
|
+
const result = path.split(".").reduce((value, key) => (isRecord(value) ? value[key] : undefined), source);
|
|
205
|
+
if (result === undefined)
|
|
206
|
+
throw new Error(`Theme reference "$${path}" was not found`);
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
function isHex(value) {
|
|
210
|
+
return /^#(?:[\da-f]{3}|[\da-f]{4}|[\da-f]{6}|[\da-f]{8})$/i.test(value);
|
|
211
|
+
}
|
|
212
|
+
function isRecord(value) {
|
|
213
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof RGBA);
|
|
214
|
+
}
|