1/** @jsxImportSource @alchemy.run/sigil */
2import { useWindowSize } from "@alchemy.run/sigil";
3import { createContext, useContext, useMemo } from "@alchemy.run/sigil/react";
4import type { ReactNode } from "react";
5import type { CliKitCapabilities } from "../types.ts";
6import { glyphsFor, theme, type KeyHint } from "../../../Util/Theme.ts";
7
8const defaults: CliKitCapabilities = {
9 input: false,
10 columns: 80,
11 rows: 24,
12 colors: false,
13 unicode: true,
14 alternateScreen: false,
15};
16
17const EnvironmentContext = createContext<CliKitCapabilities>(defaults);
18
19/**
20 * Resize-following variant, split out so the static renderer (`renderToString`
21 * for every print/format) never attaches a `resize` listener to stdout.
22 */
23type ObservingEnvironmentProps = {
24 readonly capabilities: CliKitCapabilities;
25 readonly children?: ReactNode;
26};
27
28function ObservingEnvironment({
29 capabilities,
30 children,
31}: ObservingEnvironmentProps) {
32 const window = useWindowSize();
33 const environment = useMemo(
34 () => ({ ...capabilities, columns: window.columns, rows: window.rows }),
35 [capabilities, window.columns, window.rows],
36 );
37 return (
38 <EnvironmentContext.Provider value={environment}>
39 {children}
40 </EnvironmentContext.Provider>
41 );
42}
43
44type CliEnvironmentProps = {
45 readonly capabilities: CliKitCapabilities;
46 /** Follow Sigil's terminal resize subscription for a live renderer. */
47 readonly observeWindow?: boolean;
48 readonly children?: ReactNode;
49};
50
51export function CliEnvironment({
52 capabilities,
53 observeWindow = false,
54 children,
55}: CliEnvironmentProps) {
56 return observeWindow ? (
57 <ObservingEnvironment capabilities={capabilities}>
58 {children}
59 </ObservingEnvironment>
60 ) : (
61 <EnvironmentContext.Provider value={capabilities}>
62 {children}
63 </EnvironmentContext.Provider>
64 );
65}
66
67export const useCliEnvironment = () => useContext(EnvironmentContext);
68
69export const useGlyphs = () => glyphsFor(useCliEnvironment().unicode);
70
71/** Border style matching the terminal's Unicode capability. */
72export const useBorderStyle = () =>
73 useCliEnvironment().unicode ? ("single" as const) : ("classic" as const);
74
75const asciiKeyHint: KeyHint = {
76 enter: "enter",
77 upDown: "up/down",
78 leftRight: "left/right",
79 escape: "esc",
80 space: "space",
81 tab: "tab",
82 yesNo: "y/n",
83};
84
85/** Key-hint labels for `KeyBar` footers, honoring the ASCII fallback. */
86export const useKeyGlyphs = (): KeyHint =>
87 useCliEnvironment().unicode ? theme.keyHint : asciiKeyHint;