@cssints/tui 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +205 -2
- package/dist/bomb.d.mts +86 -0
- package/dist/bomb.mjs +323 -0
- package/dist/index.d.mts +49 -0
- package/dist/index.mjs +88 -0
- package/dist/opentui.d.mts +66 -0
- package/dist/opentui.mjs +209 -0
- package/dist/system-CFyJGOUM.d.mts +118 -0
- package/dist/system.d.mts +2 -0
- package/dist/system.mjs +291 -0
- package/package.json +55 -4
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { TuiProps, TuiProps as TuiProps$1, TuiShape, TuiShape as TuiShape$1 } from "cssints/tui";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
/** The states a renderable is in. */
|
|
4
|
+
export interface TuiState {
|
|
5
|
+
hover?: boolean;
|
|
6
|
+
focus?: boolean;
|
|
7
|
+
active?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The theme in use.
|
|
11
|
+
*
|
|
12
|
+
* @returns {string} a name of `createGlobalTheme(tokens, name, ...)`, `""` for the default values
|
|
13
|
+
*/
|
|
14
|
+
export declare const theme: () => string;
|
|
15
|
+
/**
|
|
16
|
+
* Swaps the theme of every renderable an adapter has attached: their props are resolved again.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} name a name of `createGlobalTheme(tokens, name, ...)`, `""` for the default values
|
|
19
|
+
*/
|
|
20
|
+
export declare const setTheme: (name: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Registers (or replaces) a theme of the run-time table: the value of each token, by name (`color-bg` for `var(--color-bg)`), laid over
|
|
23
|
+
* the static theme `base` (`""`: the default values) for every prop a site set to that token. A token it leaves out
|
|
24
|
+
* keeps the value of `base`. When `name` is the theme in use, every attached renderable is resolved again.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} name the theme, for `setTheme(name)`
|
|
27
|
+
* @param {Record<string, string>} values a colour (`#rrggbb`, `ansi:N` or `default`) per token name
|
|
28
|
+
* @param {string} base a name of `createGlobalTheme(tokens, name, ...)`, `""` for the default values
|
|
29
|
+
*/
|
|
30
|
+
export declare const defineTheme: (name: string, values: Record<string, string>, base?: string) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Calls `fn` on every theme change.
|
|
33
|
+
*
|
|
34
|
+
* @param {Function} fn called with the name of the new theme
|
|
35
|
+
* @returns {Function} the function that stops it
|
|
36
|
+
*/
|
|
37
|
+
export declare const onTheme: (fn: (name: string) => void) => (() => void);
|
|
38
|
+
/**
|
|
39
|
+
* The props of `style` in `state` and theme `name` (default: the one in use): the props, then what the theme changes,
|
|
40
|
+
* then the `:hover`, `:focus` and `:active` overlays that are on, in that order, each with the theme's.
|
|
41
|
+
*
|
|
42
|
+
* @param {TuiShape} style a style of cssints/tui
|
|
43
|
+
* @param {TuiState} state the states that are on
|
|
44
|
+
* @param {string} name the theme
|
|
45
|
+
* @returns {TuiProps} the props, renderer-neutral
|
|
46
|
+
*/
|
|
47
|
+
export declare const resolve: (style: TuiShape$1, state?: TuiState, name?: string) => TuiProps$1;
|
|
48
|
+
//#endregion
|
|
49
|
+
export type { TuiProps, TuiShape };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//#region src/index.ts
|
|
2
|
+
const STATES = [
|
|
3
|
+
":hover",
|
|
4
|
+
":focus",
|
|
5
|
+
":active"
|
|
6
|
+
];
|
|
7
|
+
const own = (out, layer) => {
|
|
8
|
+
if (layer) {
|
|
9
|
+
for (const [k, v] of Object.entries(layer)) if (k !== "themes" && k !== "vars" && !k.startsWith(":")) out[k] = v;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
let current = "";
|
|
13
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
14
|
+
const table = /* @__PURE__ */ new Map();
|
|
15
|
+
/**
|
|
16
|
+
* The theme in use.
|
|
17
|
+
*
|
|
18
|
+
* @returns {string} a name of `createGlobalTheme(tokens, name, ...)`, `""` for the default values
|
|
19
|
+
*/
|
|
20
|
+
const theme = () => current;
|
|
21
|
+
/**
|
|
22
|
+
* Swaps the theme of every renderable an adapter has attached: their props are resolved again.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} name a name of `createGlobalTheme(tokens, name, ...)`, `""` for the default values
|
|
25
|
+
*/
|
|
26
|
+
const setTheme = (name) => {
|
|
27
|
+
if (name === current) return;
|
|
28
|
+
current = name;
|
|
29
|
+
for (const l of listeners) l(name);
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Registers (or replaces) a theme of the run-time table: the value of each token, by name (`color-bg` for `var(--color-bg)`), laid over
|
|
33
|
+
* the static theme `base` (`""`: the default values) for every prop a site set to that token. A token it leaves out
|
|
34
|
+
* keeps the value of `base`. When `name` is the theme in use, every attached renderable is resolved again.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} name the theme, for `setTheme(name)`
|
|
37
|
+
* @param {Record<string, string>} values a colour (`#rrggbb`, `ansi:N` or `default`) per token name
|
|
38
|
+
* @param {string} base a name of `createGlobalTheme(tokens, name, ...)`, `""` for the default values
|
|
39
|
+
*/
|
|
40
|
+
const defineTheme = (name, values, base = "") => {
|
|
41
|
+
table.set(name, {
|
|
42
|
+
values,
|
|
43
|
+
base
|
|
44
|
+
});
|
|
45
|
+
if (name === current) for (const l of listeners) l(name);
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Calls `fn` on every theme change.
|
|
49
|
+
*
|
|
50
|
+
* @param {Function} fn called with the name of the new theme
|
|
51
|
+
* @returns {Function} the function that stops it
|
|
52
|
+
*/
|
|
53
|
+
const onTheme = (fn) => {
|
|
54
|
+
listeners.add(fn);
|
|
55
|
+
return () => listeners.delete(fn);
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The props of `style` in `state` and theme `name` (default: the one in use): the props, then what the theme changes,
|
|
59
|
+
* then the `:hover`, `:focus` and `:active` overlays that are on, in that order, each with the theme's.
|
|
60
|
+
*
|
|
61
|
+
* @param {TuiShape} style a style of cssints/tui
|
|
62
|
+
* @param {TuiState} state the states that are on
|
|
63
|
+
* @param {string} name the theme
|
|
64
|
+
* @returns {TuiProps} the props, renderer-neutral
|
|
65
|
+
*/
|
|
66
|
+
const resolve = (style, state = {}, name = current) => {
|
|
67
|
+
const run = table.get(name);
|
|
68
|
+
const base = run ? run.base : name;
|
|
69
|
+
const themed = base ? style.themes?.[base] : void 0;
|
|
70
|
+
const vars = style.vars;
|
|
71
|
+
const out = {};
|
|
72
|
+
const tokens = (layer) => {
|
|
73
|
+
if (run && layer) {
|
|
74
|
+
for (const [k, v] of Object.entries(layer)) if (typeof v === "string" && Object.hasOwn(run.values, v)) out[k] = run.values[v];
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
own(out, style);
|
|
78
|
+
own(out, themed);
|
|
79
|
+
tokens(vars);
|
|
80
|
+
for (const s of STATES) if (state[s === ":hover" ? "hover" : s === ":focus" ? "focus" : "active"]) {
|
|
81
|
+
own(out, style[s]);
|
|
82
|
+
own(out, themed?.[s]);
|
|
83
|
+
tokens(vars?.[s]);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
export { defineTheme, onTheme, resolve, setTheme, theme };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { TuiState, resolve, setTheme, theme } from "./index.mjs";
|
|
2
|
+
import { c as Report, g as SystemTheme } from "./system-CFyJGOUM.mjs";
|
|
3
|
+
import { CliRenderer, Renderable, TerminalColors, TextNodeRenderable, ThemeMode } from "@opentui/core";
|
|
4
|
+
import { TuiProps, TuiShape } from "cssints/tui";
|
|
5
|
+
//#region src/opentui-system.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* The report of OpenTUI's palette and theme mode.
|
|
8
|
+
*
|
|
9
|
+
* @param {TerminalColors} colors what `renderer.getPalette()` gave (none yet: `undefined`)
|
|
10
|
+
* @param {ThemeMode} mode `renderer.themeMode`
|
|
11
|
+
* @returns {Report} the report for `theme.solve`
|
|
12
|
+
*/
|
|
13
|
+
export declare const reportOf: (colors: TerminalColors | undefined, mode: ThemeMode | null) => Report;
|
|
14
|
+
/**
|
|
15
|
+
* Follows the terminal's colours: sets the system theme (`setTheme`) with the fallback at once, asks OpenTUI for the
|
|
16
|
+
* palette, and rebuilds the theme when it comes, on a `palette` event and on a `theme_mode` change.
|
|
17
|
+
*
|
|
18
|
+
* @param {CliRenderer} renderer the renderer
|
|
19
|
+
* @param {SystemTheme} theme the theme of `createSystemTheme`
|
|
20
|
+
* @param {object} options `wait`: how long to wait for the palette, in ms (default 100)
|
|
21
|
+
* @returns {Promise<Function>} after the palette or `wait`, whichever is first: the function that stops following
|
|
22
|
+
*/
|
|
23
|
+
export declare const followSystemTheme: (renderer: CliRenderer, theme: SystemTheme, options?: {
|
|
24
|
+
wait?: number;
|
|
25
|
+
}) => Promise<() => void>;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/opentui.d.ts
|
|
28
|
+
/**
|
|
29
|
+
* The OpenTUI props of a style (`has` tells which props the renderable has: a box takes `backgroundColor`, a text `bg`):
|
|
30
|
+
* `color` is `fg` (`textColor` on an editable or a select), the text attributes are the bits of `attributes`, the
|
|
31
|
+
* caret is `cursorStyle` and `cursorColor` (Input, Textarea). `cursor` is not a prop: `attach` sets the mouse pointer.
|
|
32
|
+
*
|
|
33
|
+
* @param {TuiProps} p the props of `resolve()`
|
|
34
|
+
* @param {Function} has whether the renderable has a prop
|
|
35
|
+
* @returns {Record<string, unknown>} the props of an OpenTUI renderable
|
|
36
|
+
*/
|
|
37
|
+
export declare const props: (p: TuiProps, has?: (key: string) => boolean) => Record<string, unknown>;
|
|
38
|
+
type Node = Renderable | TextNodeRenderable;
|
|
39
|
+
/**
|
|
40
|
+
* Sets the props of `style` on `r` and keeps them up to date: `:hover` from `over` to `out`, `:active` from `down` to
|
|
41
|
+
* `up` (or `out`), `:focus` while it has focus (a box needs `focusable: true`), and the theme of `setTheme`. The mouse
|
|
42
|
+
* pointer is the style's `cursor` on `over`, `default` again on `out`. Handlers of your own (`onMouseOver`) still run.
|
|
43
|
+
* A focused renderable draws its `focused*` colours (`focusedBorderColor` of a box): they follow the style too. A text
|
|
44
|
+
* node (`<span>`, `<a>`) has no mouse events and no focus: it takes the props and the theme, not the states.
|
|
45
|
+
* On a renderable it has attached already, it swaps the style (cssints-332g): the states, the theme and the hooks stay,
|
|
46
|
+
* a prop only the old style set goes back to the renderable's own value, and the pointer follows the new `cursor`.
|
|
47
|
+
*
|
|
48
|
+
* @param {Renderable | TextNodeRenderable} r a renderable or a text node of OpenTUI
|
|
49
|
+
* @param {TuiShape} style a style of cssints/tui
|
|
50
|
+
* @returns {Renderable | TextNodeRenderable} `r`
|
|
51
|
+
*/
|
|
52
|
+
export declare const attach: <R extends Node>(r: R, style: TuiShape) => R;
|
|
53
|
+
/**
|
|
54
|
+
* Props to spread on an OpenTUI element of Solid or React: `<box {...use(card)} />`. The `ref` attaches the style
|
|
55
|
+
* (`attach`) when the renderable is made, before its first frame. A style that changes is an accessor,
|
|
56
|
+
* `use(() => (active() ? a : b))`: Solid calls a ref in a render effect, so the signals it reads run it again, and each
|
|
57
|
+
* run swaps the style (React calls the new ref of each render). The adapter imports no framework.
|
|
58
|
+
*
|
|
59
|
+
* @param {TuiShape | Function} style a style of cssints/tui, or a function that returns one
|
|
60
|
+
* @returns {object} `{ ref }`
|
|
61
|
+
*/
|
|
62
|
+
export declare const use: (style: TuiShape | (() => TuiShape)) => {
|
|
63
|
+
ref: (r: Node | null) => void;
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { type TuiState, resolve, setTheme, theme };
|
package/dist/opentui.mjs
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { onTheme, resolve, setTheme, theme } from "./index.mjs";
|
|
2
|
+
import { RGBA } from "@opentui/core";
|
|
3
|
+
//#region src/opentui-system.ts
|
|
4
|
+
/**
|
|
5
|
+
* The report of OpenTUI's palette and theme mode.
|
|
6
|
+
*
|
|
7
|
+
* @param {TerminalColors} colors what `renderer.getPalette()` gave (none yet: `undefined`)
|
|
8
|
+
* @param {ThemeMode} mode `renderer.themeMode`
|
|
9
|
+
* @returns {Report} the report for `theme.solve`
|
|
10
|
+
*/
|
|
11
|
+
const reportOf = (colors, mode) => ({
|
|
12
|
+
fg: colors?.defaultForeground,
|
|
13
|
+
bg: colors?.defaultBackground,
|
|
14
|
+
ansi: colors?.palette ?? [],
|
|
15
|
+
scheme: mode
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Follows the terminal's colours: sets the system theme (`setTheme`) with the fallback at once, asks OpenTUI for the
|
|
19
|
+
* palette, and rebuilds the theme when it comes, on a `palette` event and on a `theme_mode` change.
|
|
20
|
+
*
|
|
21
|
+
* @param {CliRenderer} renderer the renderer
|
|
22
|
+
* @param {SystemTheme} theme the theme of `createSystemTheme`
|
|
23
|
+
* @param {object} options `wait`: how long to wait for the palette, in ms (default 100)
|
|
24
|
+
* @returns {Promise<Function>} after the palette or `wait`, whichever is first: the function that stops following
|
|
25
|
+
*/
|
|
26
|
+
const followSystemTheme = async (renderer, theme, options = {}) => {
|
|
27
|
+
let colors;
|
|
28
|
+
const apply = () => theme.apply(reportOf(colors, renderer.themeMode), { indexed: true });
|
|
29
|
+
const onPalette = (c) => {
|
|
30
|
+
colors = c;
|
|
31
|
+
apply();
|
|
32
|
+
};
|
|
33
|
+
const query = () => renderer.getPalette({ size: 16 }).then(onPalette, () => {});
|
|
34
|
+
const onMode = () => {
|
|
35
|
+
apply();
|
|
36
|
+
query();
|
|
37
|
+
};
|
|
38
|
+
renderer.on("palette", onPalette);
|
|
39
|
+
renderer.on("theme_mode", onMode);
|
|
40
|
+
const stop = () => {
|
|
41
|
+
renderer.off("palette", onPalette);
|
|
42
|
+
renderer.off("theme_mode", onMode);
|
|
43
|
+
};
|
|
44
|
+
renderer.once("destroy", stop);
|
|
45
|
+
apply();
|
|
46
|
+
setTheme(theme.name);
|
|
47
|
+
const waited = Promise.withResolvers();
|
|
48
|
+
const timer = setTimeout(waited.resolve, options.wait ?? 100);
|
|
49
|
+
await Promise.race([query(), waited.promise]);
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
return stop;
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/opentui.ts
|
|
55
|
+
const BITS = {
|
|
56
|
+
bold: 1,
|
|
57
|
+
dim: 2,
|
|
58
|
+
italic: 4,
|
|
59
|
+
underline: 8,
|
|
60
|
+
strikethrough: 128
|
|
61
|
+
};
|
|
62
|
+
const SHAPE = {
|
|
63
|
+
auto: "default",
|
|
64
|
+
bar: "line",
|
|
65
|
+
block: "block",
|
|
66
|
+
underscore: "underline"
|
|
67
|
+
};
|
|
68
|
+
const FOCUSED = [
|
|
69
|
+
["borderColor", "focusedBorderColor"],
|
|
70
|
+
["backgroundColor", "focusedBackgroundColor"],
|
|
71
|
+
["textColor", "focusedTextColor"]
|
|
72
|
+
];
|
|
73
|
+
const drawn = (v, bg) => v === "default" ? bg ? RGBA.defaultBackground() : RGBA.defaultForeground() : typeof v === "string" && v.startsWith("ansi:") ? RGBA.fromIndex(Number(v.slice(5))) : v;
|
|
74
|
+
/**
|
|
75
|
+
* The OpenTUI props of a style (`has` tells which props the renderable has: a box takes `backgroundColor`, a text `bg`):
|
|
76
|
+
* `color` is `fg` (`textColor` on an editable or a select), the text attributes are the bits of `attributes`, the
|
|
77
|
+
* caret is `cursorStyle` and `cursorColor` (Input, Textarea). `cursor` is not a prop: `attach` sets the mouse pointer.
|
|
78
|
+
*
|
|
79
|
+
* @param {TuiProps} p the props of `resolve()`
|
|
80
|
+
* @param {Function} has whether the renderable has a prop
|
|
81
|
+
* @returns {Record<string, unknown>} the props of an OpenTUI renderable
|
|
82
|
+
*/
|
|
83
|
+
const props = (p, has = () => true) => {
|
|
84
|
+
const out = {};
|
|
85
|
+
let bits;
|
|
86
|
+
for (const [k, v] of Object.entries(p)) if (Object.hasOwn(BITS, k)) bits = (bits ?? 0) | (v ? BITS[k] : 0);
|
|
87
|
+
else if (k === "color") out[has("fg") || !has("textColor") ? "fg" : "textColor"] = drawn(v, false);
|
|
88
|
+
else if (k === "backgroundColor") out[has("backgroundColor") ? "backgroundColor" : "bg"] = drawn(v, true);
|
|
89
|
+
else if (k === "caretColor") out.cursorColor = drawn(v, false);
|
|
90
|
+
else if (k === "borderColor") out.borderColor = drawn(v, false);
|
|
91
|
+
else if (k !== "cursor" && k !== "caretShape" && k !== "caretAnimation") out[k] = v;
|
|
92
|
+
if (bits !== void 0) out.attributes = bits;
|
|
93
|
+
if (p.caretShape || p.caretAnimation) out.cursorStyle = {
|
|
94
|
+
style: SHAPE[p.caretShape ?? "auto"],
|
|
95
|
+
blinking: p.caretAnimation !== "manual"
|
|
96
|
+
};
|
|
97
|
+
return out;
|
|
98
|
+
};
|
|
99
|
+
const attached = /* @__PURE__ */ new WeakMap();
|
|
100
|
+
/**
|
|
101
|
+
* Sets the props of `style` on `r` and keeps them up to date: `:hover` from `over` to `out`, `:active` from `down` to
|
|
102
|
+
* `up` (or `out`), `:focus` while it has focus (a box needs `focusable: true`), and the theme of `setTheme`. The mouse
|
|
103
|
+
* pointer is the style's `cursor` on `over`, `default` again on `out`. Handlers of your own (`onMouseOver`) still run.
|
|
104
|
+
* A focused renderable draws its `focused*` colours (`focusedBorderColor` of a box): they follow the style too. A text
|
|
105
|
+
* node (`<span>`, `<a>`) has no mouse events and no focus: it takes the props and the theme, not the states.
|
|
106
|
+
* On a renderable it has attached already, it swaps the style (cssints-332g): the states, the theme and the hooks stay,
|
|
107
|
+
* a prop only the old style set goes back to the renderable's own value, and the pointer follows the new `cursor`.
|
|
108
|
+
*
|
|
109
|
+
* @param {Renderable | TextNodeRenderable} r a renderable or a text node of OpenTUI
|
|
110
|
+
* @param {TuiShape} style a style of cssints/tui
|
|
111
|
+
* @returns {Renderable | TextNodeRenderable} `r`
|
|
112
|
+
*/
|
|
113
|
+
const attach = (r, style) => {
|
|
114
|
+
const again = attached.get(r);
|
|
115
|
+
if (again) {
|
|
116
|
+
again(style);
|
|
117
|
+
return r;
|
|
118
|
+
}
|
|
119
|
+
const state = {};
|
|
120
|
+
const target = r;
|
|
121
|
+
const saved = /* @__PURE__ */ new Map();
|
|
122
|
+
const last = /* @__PURE__ */ new Map();
|
|
123
|
+
let pointer = false;
|
|
124
|
+
const apply = () => {
|
|
125
|
+
const next = props(resolve(style, state), (k) => k in r);
|
|
126
|
+
for (const [from, to] of FOCUSED) if (Object.hasOwn(next, from)) next[to] = next[from];
|
|
127
|
+
const attrs = next.attributes;
|
|
128
|
+
for (const k of /* @__PURE__ */ new Set([...saved.keys(), ...Object.keys(next)])) {
|
|
129
|
+
if (!(k in r)) continue;
|
|
130
|
+
if (!saved.has(k)) saved.set(k, target[k]);
|
|
131
|
+
let v = Object.hasOwn(next, k) ? next[k] : saved.get(k);
|
|
132
|
+
if (k === "attributes" && attrs !== void 0) v = saved.get(k) & -144 | attrs;
|
|
133
|
+
if (last.get(k) === v) continue;
|
|
134
|
+
last.set(k, v);
|
|
135
|
+
target[k] = v;
|
|
136
|
+
}
|
|
137
|
+
r.requestRender();
|
|
138
|
+
};
|
|
139
|
+
const set = (key, on) => {
|
|
140
|
+
if (!!state[key] === on) return false;
|
|
141
|
+
state[key] = on;
|
|
142
|
+
return true;
|
|
143
|
+
};
|
|
144
|
+
const stop = onTheme(apply);
|
|
145
|
+
if (!("onMouseEvent" in r)) {
|
|
146
|
+
attached.set(r, (next) => {
|
|
147
|
+
style = next;
|
|
148
|
+
apply();
|
|
149
|
+
});
|
|
150
|
+
const end = r.destroyRecursively.bind(r);
|
|
151
|
+
r.destroyRecursively = () => {
|
|
152
|
+
stop();
|
|
153
|
+
for (const child of r.getChildren()) child.destroyRecursively();
|
|
154
|
+
end();
|
|
155
|
+
};
|
|
156
|
+
apply();
|
|
157
|
+
return r;
|
|
158
|
+
}
|
|
159
|
+
const hooked = r;
|
|
160
|
+
const own = hooked.onMouseEvent.bind(r);
|
|
161
|
+
hooked.onMouseEvent = (event) => {
|
|
162
|
+
let changed = false;
|
|
163
|
+
if (event.type === "over") {
|
|
164
|
+
changed = set("hover", true);
|
|
165
|
+
const cursor = resolve(style, {
|
|
166
|
+
...state,
|
|
167
|
+
hover: true
|
|
168
|
+
}).cursor;
|
|
169
|
+
if (cursor) hooked.ctx.setMousePointer(cursor);
|
|
170
|
+
pointer = !!cursor;
|
|
171
|
+
} else if (event.type === "out") {
|
|
172
|
+
changed = set("hover", false);
|
|
173
|
+
changed = set("active", false) || changed;
|
|
174
|
+
if (pointer) hooked.ctx.setMousePointer("default");
|
|
175
|
+
pointer = false;
|
|
176
|
+
} else if (event.type === "down") changed = set("active", true);
|
|
177
|
+
else if (event.type === "up") changed = set("active", false);
|
|
178
|
+
if (changed || pointer) apply();
|
|
179
|
+
own(event);
|
|
180
|
+
};
|
|
181
|
+
hooked.on("focused", () => set("focus", true) && apply());
|
|
182
|
+
hooked.on("blurred", () => set("focus", false) && apply());
|
|
183
|
+
hooked.once("destroyed", stop);
|
|
184
|
+
attached.set(r, (next) => {
|
|
185
|
+
style = next;
|
|
186
|
+
if (state.hover) {
|
|
187
|
+
const cursor = resolve(style, state).cursor;
|
|
188
|
+
if (cursor || pointer) hooked.ctx.setMousePointer(cursor ?? "default");
|
|
189
|
+
pointer = !!cursor;
|
|
190
|
+
}
|
|
191
|
+
apply();
|
|
192
|
+
});
|
|
193
|
+
apply();
|
|
194
|
+
return r;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Props to spread on an OpenTUI element of Solid or React: `<box {...use(card)} />`. The `ref` attaches the style
|
|
198
|
+
* (`attach`) when the renderable is made, before its first frame. A style that changes is an accessor,
|
|
199
|
+
* `use(() => (active() ? a : b))`: Solid calls a ref in a render effect, so the signals it reads run it again, and each
|
|
200
|
+
* run swaps the style (React calls the new ref of each render). The adapter imports no framework.
|
|
201
|
+
*
|
|
202
|
+
* @param {TuiShape | Function} style a style of cssints/tui, or a function that returns one
|
|
203
|
+
* @returns {object} `{ ref }`
|
|
204
|
+
*/
|
|
205
|
+
const use = (style) => ({ ref: (r) => {
|
|
206
|
+
if (r) attach(r, typeof style === "function" ? style() : style);
|
|
207
|
+
} });
|
|
208
|
+
//#endregion
|
|
209
|
+
export { attach, followSystemTheme, props, reportOf, resolve, setTheme, theme, use };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//#region src/system.d.ts
|
|
2
|
+
/** The 16 ANSI colours, by their index. */
|
|
3
|
+
declare const SLOTS: readonly ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "brightBlack", "brightRed", "brightGreen", "brightYellow", "brightBlue", "brightMagenta", "brightCyan", "brightWhite"];
|
|
4
|
+
type Slot = (typeof SLOTS)[number];
|
|
5
|
+
/** Where a role takes its hue: an ANSI colour, or the terminal's default foreground or background. */
|
|
6
|
+
type Hue = Slot | "fg" | "bg";
|
|
7
|
+
type Appearance = "dark" | "light";
|
|
8
|
+
/** A colour token of `createTokens` (its text at run time: `var(--name)`). */
|
|
9
|
+
type ColourToken = string | {
|
|
10
|
+
readonly "~token": "color";
|
|
11
|
+
};
|
|
12
|
+
/** A colour token as a role of the system theme. */
|
|
13
|
+
interface Role {
|
|
14
|
+
/** The hue source (default `"fg"`, `"bg"` for a surface). */
|
|
15
|
+
hue?: Hue;
|
|
16
|
+
/** The tokens it sits on (each must have a role). */
|
|
17
|
+
on?: readonly ColourToken[];
|
|
18
|
+
/** The WCAG 2 ratio against each of `on`: the least for text, the exact one (to the first) for a surface. */
|
|
19
|
+
contrast?: number;
|
|
20
|
+
/** A background: placed at `contrast` from its first `on`, toward the foreground; the terminal's background without. */
|
|
21
|
+
surface?: boolean;
|
|
22
|
+
/** What the terminal draws when it reports nothing: an ANSI index, or its default colour. */
|
|
23
|
+
ansi?: number | "default";
|
|
24
|
+
}
|
|
25
|
+
/** The roles of a token set, in its shape: a role per colour token, any left out. */
|
|
26
|
+
type Roles<T> = { [K in keyof T]?: T[K] extends ColourToken ? Role : T[K] extends {
|
|
27
|
+
readonly "~token": unknown;
|
|
28
|
+
} ? never : Roles<T[K]>; };
|
|
29
|
+
/** What a terminal reported: `#rrggbb` colours (`undefined` or `null`: no answer) and its colour scheme. */
|
|
30
|
+
interface Report {
|
|
31
|
+
fg?: string | null;
|
|
32
|
+
bg?: string | null;
|
|
33
|
+
ansi?: readonly (string | null | undefined)[];
|
|
34
|
+
scheme?: Appearance | null;
|
|
35
|
+
}
|
|
36
|
+
interface SolveOptions {
|
|
37
|
+
/** Whether the renderer draws ANSI indices (`ansi:N`, `default`); without, a terminal that reports nothing keeps the static theme. */
|
|
38
|
+
indexed?: boolean;
|
|
39
|
+
/** The `COLORFGBG` variable (default: the environment's). */
|
|
40
|
+
colorfgbg?: string;
|
|
41
|
+
}
|
|
42
|
+
interface Solved {
|
|
43
|
+
appearance: Appearance;
|
|
44
|
+
/** A colour per token name (`color-bg` for `var(--color-bg)`). */
|
|
45
|
+
values: Record<string, string>;
|
|
46
|
+
/** The static theme under it: what a token without a role, or every token without a palette, keeps. */
|
|
47
|
+
base: string;
|
|
48
|
+
}
|
|
49
|
+
interface SystemOptions {
|
|
50
|
+
/** The theme's name for `setTheme` (default `"system"`). */
|
|
51
|
+
name?: string;
|
|
52
|
+
/** The app's own colour per slot, for what the terminal did not report (default: an xterm-like palette). */
|
|
53
|
+
hues?: Partial<Record<Hue, string>>;
|
|
54
|
+
/** The static theme under the system theme, per appearance (default `""`, the default values). */
|
|
55
|
+
themes?: Partial<Record<Appearance, string>>;
|
|
56
|
+
}
|
|
57
|
+
interface SystemTheme {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
/** The values of every role for a report. */
|
|
60
|
+
solve(report: Report, options?: SolveOptions): Solved;
|
|
61
|
+
/** `solve`, then `defineTheme(name, values, base)`. */
|
|
62
|
+
apply(report: Report, options?: SolveOptions): Solved;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The relative luminance of a colour (WCAG 2), as check.contrast reads it.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} hex a colour
|
|
68
|
+
* @returns {number} its luminance, 0 to 1
|
|
69
|
+
*/
|
|
70
|
+
declare const luminance: (hex: string) => number;
|
|
71
|
+
/**
|
|
72
|
+
* The WCAG 2 contrast ratio of two colours.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} a a colour
|
|
75
|
+
* @param {string} b another
|
|
76
|
+
* @returns {number} the ratio, 1 to 21
|
|
77
|
+
*/
|
|
78
|
+
declare const contrast: (a: string, b: string) => number;
|
|
79
|
+
/**
|
|
80
|
+
* A system theme: each token of `roles` takes its hue from a colour of the terminal and its lightness from its contrast
|
|
81
|
+
* rules against the tokens it sits on. Checks the roles (an `on` without a role, a cycle) and orders them.
|
|
82
|
+
*
|
|
83
|
+
* @param {object} tokens the tokens of `createTokens` (cssints/tui)
|
|
84
|
+
* @param {Roles} roles a role per colour token, in the shape of `tokens`
|
|
85
|
+
* @param {SystemOptions} options the name, the app's own hues, the static theme under it
|
|
86
|
+
* @returns {SystemTheme} the theme, for `followSystemTheme` of an adapter, or `solve` and `apply`
|
|
87
|
+
*/
|
|
88
|
+
declare const createSystemTheme: <T extends object>(tokens: T, roles: Roles<T>, options?: SystemOptions) => SystemTheme;
|
|
89
|
+
/** OSC 10 and 11 (the default colours) and OSC 4 for the 16 ANSI colours. */
|
|
90
|
+
declare const COLOURS: string;
|
|
91
|
+
/** The colours and the colour-scheme report (`CSI ? 996 n`). */
|
|
92
|
+
declare const QUERY: string;
|
|
93
|
+
/** Mode 2031 on: the terminal sends `CSI ? 997 ; 1|2 n` when its palette changes. */
|
|
94
|
+
declare const NOTIFY = "[?2031h";
|
|
95
|
+
/** Mode 2031 off. */
|
|
96
|
+
declare const UNNOTIFY = "[?2031l";
|
|
97
|
+
/** What one chunk of input held of the terminal's replies. */
|
|
98
|
+
interface Replies {
|
|
99
|
+
fg?: string;
|
|
100
|
+
bg?: string;
|
|
101
|
+
/** The ANSI colours it reported, by index. */
|
|
102
|
+
ansi: Map<number, string>;
|
|
103
|
+
/** The colour scheme of a `CSI ? 997 ; 1|2 n` (a reply to the query, or a notification of a change). */
|
|
104
|
+
scheme?: Appearance;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Takes the terminal's replies (OSC 10, 11 and 4, `CSI ? 997 n`) out of a chunk of input, as text of one char per byte.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} text the chunk, after what `tail` of the last one kept
|
|
110
|
+
* @returns {object} the replies, the rest of the input (keys, mouse), and the tail of a reply cut off at the end
|
|
111
|
+
*/
|
|
112
|
+
declare const scan: (text: string) => {
|
|
113
|
+
replies: Replies;
|
|
114
|
+
rest: string;
|
|
115
|
+
tail: string;
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
export { UNNOTIFY as _, NOTIFY as a, luminance as b, Report as c, SLOTS as d, Slot as f, SystemTheme as g, SystemOptions as h, Hue as i, Role as l, Solved as m, COLOURS as n, QUERY as o, SolveOptions as p, ColourToken as r, Replies as s, Appearance as t, Roles as u, contrast as v, scan as x, createSystemTheme as y };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as UNNOTIFY, a as NOTIFY, b as luminance, c as Report, d as SLOTS, f as Slot, g as SystemTheme, h as SystemOptions, i as Hue, l as Role, m as Solved, n as COLOURS, o as QUERY, p as SolveOptions, r as ColourToken, s as Replies, t as Appearance, u as Roles, v as contrast, x as scan, y as createSystemTheme } from "./system-CFyJGOUM.mjs";
|
|
2
|
+
export { Appearance, COLOURS, ColourToken, Hue, NOTIFY, QUERY, Replies, Report, Role, Roles, SLOTS, Slot, SolveOptions, Solved, SystemOptions, SystemTheme, UNNOTIFY, contrast, createSystemTheme, luminance, scan };
|