@tooee/themes 0.7.3 → 0.8.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/dist/context.d.ts +4 -3
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +4 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +19 -64
- package/dist/loader.js.map +1 -1
- package/dist/types.d.ts +56 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +31 -80
- package/dist/types.js.map +1 -1
- package/package.json +2 -4
- package/src/context.tsx +5 -4
- package/src/index.ts +1 -6
- package/src/loader.ts +29 -70
- package/src/types.ts +39 -85
- package/dist/close-button.d.ts +0 -17
- package/dist/close-button.d.ts.map +0 -1
- package/dist/close-button.js +0 -23
- package/dist/close-button.js.map +0 -1
- package/dist/theme-picker.d.ts +0 -14
- package/dist/theme-picker.d.ts.map +0 -1
- package/dist/theme-picker.js +0 -109
- package/dist/theme-picker.js.map +0 -1
- package/src/close-button.tsx +0 -45
- package/src/theme-picker.tsx +0 -189
package/src/loader.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SyntaxStyle } from "@opentui/core";
|
|
2
2
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import type { ColorMode } from "@tooee/config";
|
|
4
5
|
import { resolveTheme } from "./types.js";
|
|
5
6
|
import type { ThemeJSON, ResolvedTheme } from "./types.js";
|
|
6
7
|
import { buildSyntaxStyle } from "./syntax-rules.js";
|
|
@@ -11,7 +12,7 @@ import { buildSyntaxStyle } from "./syntax-rules.js";
|
|
|
11
12
|
|
|
12
13
|
export interface Theme {
|
|
13
14
|
name: string;
|
|
14
|
-
mode:
|
|
15
|
+
mode: ColorMode;
|
|
15
16
|
colors: ResolvedTheme;
|
|
16
17
|
syntax: SyntaxStyle;
|
|
17
18
|
}
|
|
@@ -34,8 +35,13 @@ const loadJsonThemesFromDir = function loadJsonThemesFromDir(
|
|
|
34
35
|
const name = path.basename(file, ".json");
|
|
35
36
|
try {
|
|
36
37
|
const content = readFileSync(path.join(dir, file), "utf-8");
|
|
37
|
-
//
|
|
38
|
-
//
|
|
38
|
+
// SAFETY: the file is not validated here on purpose. Its only consumer is
|
|
39
|
+
// `resolveTheme`, which reads each key with a per-key fallback, and
|
|
40
|
+
// `buildTheme` catches every malformed value and returns the hardcoded
|
|
41
|
+
// theme, so a document that is not a ThemeJSON cannot escape this module.
|
|
42
|
+
// A strict decoder would also drop malformed files from `getThemeNames`,
|
|
43
|
+
// which is a behaviour change reserved for a separate decision.
|
|
44
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- tolerant consumer boundary; see the SAFETY note
|
|
39
45
|
target.set(name, JSON.parse(content) as ThemeJSON);
|
|
40
46
|
} catch {
|
|
41
47
|
// skip invalid files
|
|
@@ -89,85 +95,38 @@ export const getThemeNames = function getThemeNames(): string[] {
|
|
|
89
95
|
// ---------------------------------------------------------------------------
|
|
90
96
|
|
|
91
97
|
export const DEFAULT_THEME_NAME = "tokyonight";
|
|
92
|
-
export const DEFAULT_MODE:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
diffHighlightAdded: "#b8db87",
|
|
110
|
-
diffHighlightRemoved: "#e26a75",
|
|
111
|
-
diffHunkHeader: "#828bb8",
|
|
112
|
-
diffLineNumber: "#222436",
|
|
113
|
-
diffRemoved: "#c53b53",
|
|
114
|
-
diffRemovedBg: "#37222c",
|
|
115
|
-
diffRemovedLineNumberBg: "#2d1f26",
|
|
116
|
-
error: "#f7768e",
|
|
117
|
-
info: "#7aa2f7",
|
|
118
|
-
markdownBlockQuote: "#e0af68",
|
|
119
|
-
markdownCode: "#9ece6a",
|
|
120
|
-
markdownCodeBlock: "#c0caf5",
|
|
121
|
-
markdownEmph: "#e0af68",
|
|
122
|
-
markdownHeading: "#bb9af7",
|
|
123
|
-
markdownHorizontalRule: "#565f89",
|
|
124
|
-
markdownImage: "#7aa2f7",
|
|
125
|
-
markdownImageText: "#7dcfff",
|
|
126
|
-
markdownLink: "#7aa2f7",
|
|
127
|
-
markdownLinkText: "#7dcfff",
|
|
128
|
-
markdownListEnumeration: "#7dcfff",
|
|
129
|
-
markdownListItem: "#7aa2f7",
|
|
130
|
-
markdownStrong: "#ff966c",
|
|
131
|
-
markdownText: "#c0caf5",
|
|
132
|
-
primary: "#7aa2f7",
|
|
133
|
-
secondary: "#bb9af7",
|
|
134
|
-
selection: "#1e2030",
|
|
135
|
-
success: "#9ece6a",
|
|
136
|
-
syntaxComment: "#565f89",
|
|
137
|
-
syntaxFunction: "#7aa2f7",
|
|
138
|
-
syntaxKeyword: "#bb9af7",
|
|
139
|
-
syntaxNumber: "#ff9e64",
|
|
140
|
-
syntaxOperator: "#89ddff",
|
|
141
|
-
syntaxPunctuation: "#a9b1d6",
|
|
142
|
-
syntaxString: "#9ece6a",
|
|
143
|
-
syntaxType: "#2ac3de",
|
|
144
|
-
syntaxVariable: "#c0caf5",
|
|
145
|
-
text: "#c0caf5",
|
|
146
|
-
textMuted: "#565f89",
|
|
147
|
-
warning: "#e0af68",
|
|
148
|
-
};
|
|
149
|
-
return { colors, mode: DEFAULT_MODE, name: DEFAULT_THEME_NAME, syntax: buildSyntaxStyle(colors) };
|
|
150
|
-
})();
|
|
151
|
-
|
|
152
|
-
export const defaultTheme: Theme = hardcodedDefaultTheme;
|
|
153
|
-
|
|
154
|
-
export const buildTheme = function buildTheme(name: string, mode: "dark" | "light"): Theme {
|
|
98
|
+
export const DEFAULT_MODE: ColorMode = "dark";
|
|
99
|
+
|
|
100
|
+
// SAFETY: this package owns the bundled theme document. resolveTheme validates
|
|
101
|
+
// every consumed color and fills every omitted key from FALLBACKS.
|
|
102
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- trusted package-owned JSON boundary
|
|
103
|
+
const bundledDefaultThemeJson = JSON.parse(
|
|
104
|
+
readFileSync(new URL("../themes/tokyonight.json", import.meta.url), "utf-8"),
|
|
105
|
+
) as ThemeJSON;
|
|
106
|
+
|
|
107
|
+
const buildBundledDefaultTheme = function buildBundledDefaultTheme(mode: ColorMode): Theme {
|
|
108
|
+
const colors = resolveTheme(bundledDefaultThemeJson, mode);
|
|
109
|
+
return { colors, mode, name: DEFAULT_THEME_NAME, syntax: buildSyntaxStyle(colors) };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const defaultTheme: Theme = buildBundledDefaultTheme(DEFAULT_MODE);
|
|
113
|
+
|
|
114
|
+
export const buildTheme = function buildTheme(name: string, mode: ColorMode): Theme {
|
|
155
115
|
const themes = loadThemes();
|
|
156
116
|
const json = themes.get(name);
|
|
157
117
|
if (!json) {
|
|
158
|
-
// Fall back to tokyonight, then first available, then
|
|
118
|
+
// Fall back to tokyonight, then first available, then the bundled default.
|
|
159
119
|
const fallbackJson = themes.get(DEFAULT_THEME_NAME) ?? themes.values().next().value;
|
|
160
120
|
if (fallbackJson) {
|
|
161
121
|
const resolved = resolveTheme(fallbackJson, mode);
|
|
162
122
|
return { colors: resolved, mode, name, syntax: buildSyntaxStyle(resolved) };
|
|
163
123
|
}
|
|
164
|
-
|
|
165
|
-
return hardcodedDefaultTheme;
|
|
124
|
+
return buildBundledDefaultTheme(mode);
|
|
166
125
|
}
|
|
167
126
|
try {
|
|
168
127
|
const resolved = resolveTheme(json, mode);
|
|
169
128
|
return { colors: resolved, mode, name, syntax: buildSyntaxStyle(resolved) };
|
|
170
129
|
} catch {
|
|
171
|
-
return
|
|
130
|
+
return buildBundledDefaultTheme(mode);
|
|
172
131
|
}
|
|
173
132
|
};
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ColorMode } from "@tooee/config";
|
|
2
|
+
|
|
1
3
|
// ---------------------------------------------------------------------------
|
|
2
4
|
// Theme JSON format (OpenCode-compatible)
|
|
3
5
|
// ---------------------------------------------------------------------------
|
|
@@ -78,64 +80,8 @@ export interface ResolvedTheme {
|
|
|
78
80
|
syntaxPunctuation: string;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
//
|
|
82
|
-
export const
|
|
83
|
-
"primary",
|
|
84
|
-
"secondary",
|
|
85
|
-
"accent",
|
|
86
|
-
"error",
|
|
87
|
-
"warning",
|
|
88
|
-
"success",
|
|
89
|
-
"info",
|
|
90
|
-
"text",
|
|
91
|
-
"textMuted",
|
|
92
|
-
"background",
|
|
93
|
-
"backgroundPanel",
|
|
94
|
-
"backgroundElement",
|
|
95
|
-
"border",
|
|
96
|
-
"borderActive",
|
|
97
|
-
"borderSubtle",
|
|
98
|
-
"cursorLine",
|
|
99
|
-
"selection",
|
|
100
|
-
"diffAdded",
|
|
101
|
-
"diffRemoved",
|
|
102
|
-
"diffContext",
|
|
103
|
-
"diffHunkHeader",
|
|
104
|
-
"diffHighlightAdded",
|
|
105
|
-
"diffHighlightRemoved",
|
|
106
|
-
"diffAddedBg",
|
|
107
|
-
"diffRemovedBg",
|
|
108
|
-
"diffContextBg",
|
|
109
|
-
"diffLineNumber",
|
|
110
|
-
"diffAddedLineNumberBg",
|
|
111
|
-
"diffRemovedLineNumberBg",
|
|
112
|
-
"markdownText",
|
|
113
|
-
"markdownHeading",
|
|
114
|
-
"markdownLink",
|
|
115
|
-
"markdownLinkText",
|
|
116
|
-
"markdownCode",
|
|
117
|
-
"markdownBlockQuote",
|
|
118
|
-
"markdownEmph",
|
|
119
|
-
"markdownStrong",
|
|
120
|
-
"markdownHorizontalRule",
|
|
121
|
-
"markdownListItem",
|
|
122
|
-
"markdownListEnumeration",
|
|
123
|
-
"markdownImage",
|
|
124
|
-
"markdownImageText",
|
|
125
|
-
"markdownCodeBlock",
|
|
126
|
-
"syntaxComment",
|
|
127
|
-
"syntaxKeyword",
|
|
128
|
-
"syntaxFunction",
|
|
129
|
-
"syntaxVariable",
|
|
130
|
-
"syntaxString",
|
|
131
|
-
"syntaxNumber",
|
|
132
|
-
"syntaxType",
|
|
133
|
-
"syntaxOperator",
|
|
134
|
-
"syntaxPunctuation",
|
|
135
|
-
];
|
|
136
|
-
|
|
137
|
-
// Fallbacks used when a theme key is missing
|
|
138
|
-
export const FALLBACKS: Record<string, string> = {
|
|
83
|
+
// Fallbacks used when a theme key is missing: a complete theme in its own right.
|
|
84
|
+
export const FALLBACKS = {
|
|
139
85
|
accent: "#808080",
|
|
140
86
|
background: "#1e1e1e",
|
|
141
87
|
backgroundElement: "#1e1e1e",
|
|
@@ -188,44 +134,54 @@ export const FALLBACKS: Record<string, string> = {
|
|
|
188
134
|
text: "#d4d4d4",
|
|
189
135
|
textMuted: "#808080",
|
|
190
136
|
warning: "#808080",
|
|
191
|
-
}
|
|
137
|
+
} satisfies Record<keyof ResolvedTheme, string>;
|
|
138
|
+
|
|
139
|
+
export const RESOLVED_KEYS =
|
|
140
|
+
// SAFETY: FALLBACKS satisfies a complete ResolvedTheme Record, so Object.keys contains only its keys.
|
|
141
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Object.keys does not preserve Record key types
|
|
142
|
+
Object.keys(FALLBACKS) as (keyof ResolvedTheme)[];
|
|
192
143
|
|
|
193
144
|
// ---------------------------------------------------------------------------
|
|
194
145
|
// Resolution
|
|
195
146
|
// ---------------------------------------------------------------------------
|
|
196
147
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
148
|
+
/** A mode-specific colour pair is an object; a single colour is a string. */
|
|
149
|
+
const isVariant = function isVariant(value: ColorValue): value is Variant {
|
|
150
|
+
return value instanceof Object;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const resolveTheme = function resolveTheme(json: ThemeJSON, mode: ColorMode): ResolvedTheme {
|
|
201
154
|
const defs = json.defs ?? {};
|
|
202
155
|
|
|
203
156
|
const resolveColor = function resolveColor(c: ColorValue, seen = new Set<string>()): string {
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (defs[c] !== undefined && defs[c] !== null) {
|
|
215
|
-
return resolveColor(defs[c], new Set(seen).add(c));
|
|
216
|
-
}
|
|
217
|
-
if (json.theme[c] !== undefined) {
|
|
218
|
-
return resolveColor(json.theme[c], new Set(seen).add(c));
|
|
219
|
-
}
|
|
157
|
+
if (isVariant(c)) {
|
|
158
|
+
return resolveColor(c[mode], seen);
|
|
159
|
+
}
|
|
160
|
+
if (c === "transparent" || c === "none") {
|
|
161
|
+
return "#00000000";
|
|
162
|
+
}
|
|
163
|
+
if (c.startsWith("#")) {
|
|
164
|
+
return c;
|
|
165
|
+
}
|
|
166
|
+
if (seen.has(c)) {
|
|
220
167
|
return "#808080";
|
|
221
168
|
}
|
|
222
|
-
|
|
169
|
+
if (defs[c] !== undefined && defs[c] !== null) {
|
|
170
|
+
return resolveColor(defs[c], new Set(seen).add(c));
|
|
171
|
+
}
|
|
172
|
+
if (json.theme[c] !== undefined) {
|
|
173
|
+
return resolveColor(json.theme[c], new Set(seen).add(c));
|
|
174
|
+
}
|
|
175
|
+
return "#808080";
|
|
223
176
|
};
|
|
224
177
|
|
|
225
|
-
|
|
178
|
+
// Start from the complete fallback theme, then resolve every key the JSON defines.
|
|
179
|
+
const result: ResolvedTheme = { ...FALLBACKS };
|
|
226
180
|
for (const key of RESOLVED_KEYS) {
|
|
227
181
|
const val = json.theme[key];
|
|
228
|
-
|
|
182
|
+
if (val !== undefined) {
|
|
183
|
+
result[key] = resolveColor(val);
|
|
184
|
+
}
|
|
229
185
|
}
|
|
230
186
|
// Dynamic fallbacks that reference other resolved keys
|
|
231
187
|
if (json.theme.cursorLine === undefined) {
|
|
@@ -234,7 +190,5 @@ export const resolveTheme = function resolveTheme(
|
|
|
234
190
|
if (json.theme.selection === undefined) {
|
|
235
191
|
result.selection = result.backgroundPanel;
|
|
236
192
|
}
|
|
237
|
-
|
|
238
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- RESOLVED_KEYS drives every required key and defaults missing values
|
|
239
|
-
return result as unknown as ResolvedTheme;
|
|
193
|
+
return result;
|
|
240
194
|
};
|
package/dist/close-button.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
interface CloseButtonProps {
|
|
2
|
-
/** Called when the button is clicked (left mouse button). */
|
|
3
|
-
onClose: () => void;
|
|
4
|
-
/** Glyph to render (default "✕"). */
|
|
5
|
-
glyph?: string;
|
|
6
|
-
/** Foreground colour (defaults to theme.textMuted). */
|
|
7
|
-
color?: string;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Reusable clickable close control for overlays.
|
|
11
|
-
*
|
|
12
|
-
* Renders a small themed glyph that calls `onClose` on left-click. Purely
|
|
13
|
-
* additive — overlays keep their existing Escape / cancel keyboard paths.
|
|
14
|
-
*/
|
|
15
|
-
export declare const CloseButton: ({ onClose, glyph, color, }: CloseButtonProps) => React.ReactNode;
|
|
16
|
-
export {};
|
|
17
|
-
//# sourceMappingURL=close-button.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"close-button.d.ts","sourceRoot":"","sources":["../src/close-button.tsx"],"names":[],"mappings":"AAIA,UAAU,gBAAgB;IACxB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,+BAIrB,gBAAgB,KAAG,KAAK,CAAC,SAqB3B,CAAC"}
|
package/dist/close-button.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
|
-
import { useCallback } from "react";
|
|
3
|
-
import { useTheme } from "./context.js";
|
|
4
|
-
/**
|
|
5
|
-
* Reusable clickable close control for overlays.
|
|
6
|
-
*
|
|
7
|
-
* Renders a small themed glyph that calls `onClose` on left-click. Purely
|
|
8
|
-
* additive — overlays keep their existing Escape / cancel keyboard paths.
|
|
9
|
-
*/
|
|
10
|
-
export const CloseButton = function CloseButton({ onClose, glyph = "✕", color, }) {
|
|
11
|
-
const { theme } = useTheme();
|
|
12
|
-
const handleMouseDown = useCallback((event) => {
|
|
13
|
-
// Left button only; let other buttons bubble.
|
|
14
|
-
if (event.button !== 0) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
event.preventDefault();
|
|
18
|
-
event.stopPropagation();
|
|
19
|
-
onClose();
|
|
20
|
-
}, [onClose]);
|
|
21
|
-
return (_jsx("box", { flexShrink: 0, paddingLeft: 1, onMouseDown: handleMouseDown, children: _jsx("text", { content: glyph, fg: color ?? theme.textMuted }) }));
|
|
22
|
-
};
|
|
23
|
-
//# sourceMappingURL=close-button.js.map
|
package/dist/close-button.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"close-button.js","sourceRoot":"","sources":["../src/close-button.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAWxC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,WAAW,CAAC,EAC9C,OAAO,EACP,KAAK,GAAG,GAAG,EACX,KAAK,GACY;IACjB,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC;IAE7B,MAAM,eAAe,GAAG,WAAW,CACjC,CAAC,KAAiB,EAAE,EAAE;QACpB,8CAA8C;QAC9C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,KAAK,CAAC,eAAe,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC,EACD,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,OAAO,CACL,cAAK,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,eAAe,YAC9D,eAAM,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,IAAI,KAAK,CAAC,SAAS,GAAI,GAClD,CACP,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/theme-picker.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export interface ThemePickerEntry {
|
|
2
|
-
id: string;
|
|
3
|
-
title: string;
|
|
4
|
-
}
|
|
5
|
-
interface ThemePickerProps {
|
|
6
|
-
entries: ThemePickerEntry[];
|
|
7
|
-
currentTheme: string;
|
|
8
|
-
onSelect: (name: string) => void;
|
|
9
|
-
onClose: () => void;
|
|
10
|
-
onNavigate: (name: string) => void;
|
|
11
|
-
}
|
|
12
|
-
export declare const ThemePicker: ({ entries, currentTheme, onSelect, onClose, onNavigate, }: ThemePickerProps) => React.ReactNode;
|
|
13
|
-
export {};
|
|
14
|
-
//# sourceMappingURL=theme-picker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"theme-picker.d.ts","sourceRoot":"","sources":["../src/theme-picker.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,gBAAgB;IACxB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,eAAO,MAAM,WAAW,8DAMrB,gBAAgB,KAAG,KAAK,CAAC,SAmK3B,CAAC"}
|
package/dist/theme-picker.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
|
-
import { useState, useMemo, useCallback } from "react";
|
|
3
|
-
import { useCommand } from "@tooee/commands";
|
|
4
|
-
import { fuzzyMatch } from "@tooee/fuzzy";
|
|
5
|
-
import { useTheme } from "./context.js";
|
|
6
|
-
import { CloseButton } from "./close-button.js";
|
|
7
|
-
export const ThemePicker = function ThemePicker({ entries, currentTheme, onSelect, onClose, onNavigate, }) {
|
|
8
|
-
const { theme } = useTheme();
|
|
9
|
-
const [filter, setFilter] = useState("");
|
|
10
|
-
const [activeIndex, setActiveIndex] = useState(() => {
|
|
11
|
-
const idx = entries.findIndex((e) => e.id === currentTheme);
|
|
12
|
-
return Math.max(idx, 0);
|
|
13
|
-
});
|
|
14
|
-
const filtered = useMemo(() => {
|
|
15
|
-
if (!filter) {
|
|
16
|
-
return entries;
|
|
17
|
-
}
|
|
18
|
-
const results = [];
|
|
19
|
-
for (const entry of entries) {
|
|
20
|
-
const score = fuzzyMatch(filter, entry.title);
|
|
21
|
-
if (score !== null) {
|
|
22
|
-
results.push({ entry, score });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
results.sort((a, b) => b.score - a.score);
|
|
26
|
-
return results.map((r) => r.entry);
|
|
27
|
-
}, [entries, filter]);
|
|
28
|
-
const handleSelect = useCallback(() => {
|
|
29
|
-
const item = filtered[activeIndex];
|
|
30
|
-
if (item !== undefined) {
|
|
31
|
-
onSelect(item.id);
|
|
32
|
-
}
|
|
33
|
-
}, [filtered, activeIndex, onSelect]);
|
|
34
|
-
const navigateTo = useCallback((index) => {
|
|
35
|
-
setActiveIndex(index);
|
|
36
|
-
const item = filtered[index];
|
|
37
|
-
if (item !== undefined) {
|
|
38
|
-
onNavigate(item.id);
|
|
39
|
-
}
|
|
40
|
-
}, [filtered, onNavigate]);
|
|
41
|
-
const moveUp = useCallback(() => {
|
|
42
|
-
navigateTo(Math.max(0, activeIndex - 1));
|
|
43
|
-
}, [activeIndex, navigateTo]);
|
|
44
|
-
const moveDown = useCallback(() => {
|
|
45
|
-
navigateTo(Math.min(filtered.length - 1, activeIndex + 1));
|
|
46
|
-
}, [activeIndex, filtered.length, navigateTo]);
|
|
47
|
-
useCommand({
|
|
48
|
-
handler: onClose,
|
|
49
|
-
hidden: true,
|
|
50
|
-
hotkey: "Escape",
|
|
51
|
-
id: "theme-picker:close",
|
|
52
|
-
modes: ["insert", "cursor"],
|
|
53
|
-
title: "Close theme picker",
|
|
54
|
-
});
|
|
55
|
-
useCommand({
|
|
56
|
-
handler: handleSelect,
|
|
57
|
-
hidden: true,
|
|
58
|
-
hotkey: "Enter",
|
|
59
|
-
id: "theme-picker:select",
|
|
60
|
-
modes: ["insert", "cursor"],
|
|
61
|
-
title: "Select theme",
|
|
62
|
-
});
|
|
63
|
-
useCommand({
|
|
64
|
-
handler: moveUp,
|
|
65
|
-
hidden: true,
|
|
66
|
-
hotkey: "up",
|
|
67
|
-
id: "theme-picker:move-up",
|
|
68
|
-
modes: ["insert", "cursor"],
|
|
69
|
-
title: "Move up",
|
|
70
|
-
});
|
|
71
|
-
useCommand({
|
|
72
|
-
handler: moveDown,
|
|
73
|
-
hidden: true,
|
|
74
|
-
hotkey: "down",
|
|
75
|
-
id: "theme-picker:move-down",
|
|
76
|
-
modes: ["insert", "cursor"],
|
|
77
|
-
title: "Move down",
|
|
78
|
-
});
|
|
79
|
-
return (_jsxs("box", { position: "absolute", left: "20%", right: "20%", top: 2, maxHeight: "60%", flexDirection: "column", backgroundColor: theme.backgroundPanel, border: true, borderColor: theme.border, children: [_jsxs("box", { flexDirection: "row", paddingLeft: 1, paddingRight: 1, height: 1, children: [_jsx("text", { content: "\uD83C\uDFA8 ", fg: theme.accent }), _jsx("input", { focused: true, placeholder: "Filter themes...", onSubmit: handleSelect, onInput: (value) => {
|
|
80
|
-
setFilter(value);
|
|
81
|
-
setActiveIndex(0);
|
|
82
|
-
// Preview first match
|
|
83
|
-
if (value) {
|
|
84
|
-
const results = [];
|
|
85
|
-
for (const entry of entries) {
|
|
86
|
-
const score = fuzzyMatch(value, entry.title);
|
|
87
|
-
if (score !== null) {
|
|
88
|
-
results.push({ entry, score });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
results.sort((a, b) => b.score - a.score);
|
|
92
|
-
if (results.length > 0) {
|
|
93
|
-
onNavigate(results[0].entry.id);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
else if (entries.length > 0) {
|
|
97
|
-
onNavigate(entries[0].id);
|
|
98
|
-
}
|
|
99
|
-
}, backgroundColor: "transparent", focusedBackgroundColor: "transparent", textColor: theme.text, placeholderColor: theme.textMuted, cursorColor: theme.accent, style: { flexGrow: 1 } }), _jsx("text", { content: ` ${filtered.length}`, fg: theme.textMuted }), _jsx(CloseButton, { onClose: onClose })] }), _jsx("box", { height: 1, width: "100%", backgroundColor: theme.border }), _jsx("scrollbox", { focused: false, style: { flexGrow: 1 }, children: filtered.map((entry, i) => (_jsx("box", { flexDirection: "row", paddingLeft: 1, paddingRight: 1, height: 1, backgroundColor: i === activeIndex ? theme.backgroundElement : undefined, onMouseDown: (event) => {
|
|
100
|
-
// Left-click applies the theme — same code path as Enter.
|
|
101
|
-
if (event.button !== 0) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
event.preventDefault();
|
|
105
|
-
event.stopPropagation();
|
|
106
|
-
onSelect(entry.id);
|
|
107
|
-
}, children: _jsx("text", { content: entry.title, fg: theme.text, style: { flexGrow: 1 } }) }, entry.id))) })] }));
|
|
108
|
-
};
|
|
109
|
-
//# sourceMappingURL=theme-picker.js.map
|
package/dist/theme-picker.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"theme-picker.js","sourceRoot":"","sources":["../src/theme-picker.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAehD,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,WAAW,CAAC,EAC9C,OAAO,EACP,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,UAAU,GACO;IACjB,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC;IAC7B,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE;QAClD,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,YAAY,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,MAAM,OAAO,GAAiD,EAAE,CAAC;QACjE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YAC9C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAC1C,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAEtB,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;QACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;QACnC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpB,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEtC,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,KAAa,EAAE,EAAE;QAChB,cAAc,CAAC,KAAK,CAAC,CAAC;QACtB,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EACD,CAAC,QAAQ,EAAE,UAAU,CAAC,CACvB,CAAC;IAEF,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC9B,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;IAE9B,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;QAChC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IAE/C,UAAU,CAAC;QACT,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,QAAQ;QAChB,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAC3B,KAAK,EAAE,oBAAoB;KAC5B,CAAC,CAAC;IACH,UAAU,CAAC;QACT,OAAO,EAAE,YAAY;QACrB,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,OAAO;QACf,EAAE,EAAE,qBAAqB;QACzB,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAC3B,KAAK,EAAE,cAAc;KACtB,CAAC,CAAC;IACH,UAAU,CAAC;QACT,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,IAAI;QACZ,EAAE,EAAE,sBAAsB;QAC1B,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAC3B,KAAK,EAAE,SAAS;KACjB,CAAC,CAAC;IACH,UAAU,CAAC;QACT,OAAO,EAAE,QAAQ;QACjB,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,MAAM;QACd,EAAE,EAAE,wBAAwB;QAC5B,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAC3B,KAAK,EAAE,WAAW;KACnB,CAAC,CAAC;IAEH,OAAO,CACL,eACE,QAAQ,EAAC,UAAU,EACnB,IAAI,EAAC,KAAK,EACV,KAAK,EAAC,KAAK,EACX,GAAG,EAAE,CAAC,EACN,SAAS,EAAC,KAAK,EACf,aAAa,EAAC,QAAQ,EACtB,eAAe,EAAE,KAAK,CAAC,eAAe,EACtC,MAAM,QACN,WAAW,EAAE,KAAK,CAAC,MAAM,aAGzB,eAAK,aAAa,EAAC,KAAK,EAAC,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,aACjE,eAAM,OAAO,EAAC,eAAK,EAAC,EAAE,EAAE,KAAK,CAAC,MAAM,GAAI,EACxC,gBACE,OAAO,QACP,WAAW,EAAC,kBAAkB,EAC9B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,CAAC,KAAa,EAAE,EAAE;4BACzB,SAAS,CAAC,KAAK,CAAC,CAAC;4BACjB,cAAc,CAAC,CAAC,CAAC,CAAC;4BAClB,sBAAsB;4BACtB,IAAI,KAAK,EAAE,CAAC;gCACV,MAAM,OAAO,GAAiD,EAAE,CAAC;gCACjE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;oCAC5B,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;oCAC7C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;wCACnB,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;oCACjC,CAAC;gCACH,CAAC;gCACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;gCAC1C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oCACvB,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gCAClC,CAAC;4BACH,CAAC;iCAAM,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gCAC9B,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;4BAC5B,CAAC;wBACH,CAAC,EACD,eAAe,EAAC,aAAa,EAC7B,sBAAsB,EAAC,aAAa,EACpC,SAAS,EAAE,KAAK,CAAC,IAAI,EACrB,gBAAgB,EAAE,KAAK,CAAC,SAAS,EACjC,WAAW,EAAE,KAAK,CAAC,MAAM,EACzB,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,GACtB,EACF,eAAM,OAAO,EAAE,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,SAAS,GAAI,EAC7D,KAAC,WAAW,IAAC,OAAO,EAAE,OAAO,GAAI,IAC7B,EAGN,cAAK,MAAM,EAAE,CAAC,EAAE,KAAK,EAAC,MAAM,EAAC,eAAe,EAAE,KAAK,CAAC,MAAM,GAAI,EAG9D,oBAAW,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,YAC9C,QAAQ,CAAC,GAAG,CACX,CAAC,KAAK,EAAE,CAAC,EAAmB,EAAE,CAAC,CAC7B,cAEE,aAAa,EAAC,KAAK,EACnB,WAAW,EAAE,CAAC,EACd,YAAY,EAAE,CAAC,EACf,MAAM,EAAE,CAAC,EACT,eAAe,EAAE,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,EACxE,WAAW,EAAE,CAAC,KAAK,EAAE,EAAE;wBACrB,0DAA0D;wBAC1D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;4BACvB,OAAO;wBACT,CAAC;wBACD,KAAK,CAAC,cAAc,EAAE,CAAC;wBACvB,KAAK,CAAC,eAAe,EAAE,CAAC;wBACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;oBACrB,CAAC,YAED,eAAM,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAI,IAhBjE,KAAK,CAAC,EAAE,CAiBT,CACP,CACF,GACS,IACR,CACP,CAAC;AACJ,CAAC,CAAC"}
|
package/src/close-button.tsx
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
2
|
-
import type { MouseEvent } from "@opentui/core";
|
|
3
|
-
import { useTheme } from "./context.js";
|
|
4
|
-
|
|
5
|
-
interface CloseButtonProps {
|
|
6
|
-
/** Called when the button is clicked (left mouse button). */
|
|
7
|
-
onClose: () => void;
|
|
8
|
-
/** Glyph to render (default "✕"). */
|
|
9
|
-
glyph?: string;
|
|
10
|
-
/** Foreground colour (defaults to theme.textMuted). */
|
|
11
|
-
color?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Reusable clickable close control for overlays.
|
|
16
|
-
*
|
|
17
|
-
* Renders a small themed glyph that calls `onClose` on left-click. Purely
|
|
18
|
-
* additive — overlays keep their existing Escape / cancel keyboard paths.
|
|
19
|
-
*/
|
|
20
|
-
export const CloseButton = function CloseButton({
|
|
21
|
-
onClose,
|
|
22
|
-
glyph = "✕",
|
|
23
|
-
color,
|
|
24
|
-
}: CloseButtonProps): React.ReactNode {
|
|
25
|
-
const { theme } = useTheme();
|
|
26
|
-
|
|
27
|
-
const handleMouseDown = useCallback(
|
|
28
|
-
(event: MouseEvent) => {
|
|
29
|
-
// Left button only; let other buttons bubble.
|
|
30
|
-
if (event.button !== 0) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
event.preventDefault();
|
|
34
|
-
event.stopPropagation();
|
|
35
|
-
onClose();
|
|
36
|
-
},
|
|
37
|
-
[onClose],
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<box flexShrink={0} paddingLeft={1} onMouseDown={handleMouseDown}>
|
|
42
|
-
<text content={glyph} fg={color ?? theme.textMuted} />
|
|
43
|
-
</box>
|
|
44
|
-
);
|
|
45
|
-
};
|