ink-cartridge 3.6.1
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 +249 -0
- package/dist/binary-storage/BinaryStorage.d.ts +308 -0
- package/dist/binary-storage/BinaryStorage.js +548 -0
- package/dist/binary-storage/StreamingReader.d.ts +53 -0
- package/dist/binary-storage/StreamingReader.js +530 -0
- package/dist/binary-storage/index.d.ts +91 -0
- package/dist/binary-storage/index.js +91 -0
- package/dist/binary-storage/types.d.ts +400 -0
- package/dist/binary-storage/types.js +15 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +214 -0
- package/dist/cli/initTheme.d.ts +14 -0
- package/dist/cli/initTheme.js +124 -0
- package/dist/cli/makeLanguageType.d.ts +46 -0
- package/dist/cli/makeLanguageType.js +264 -0
- package/dist/cli/makeThemeType.d.ts +43 -0
- package/dist/cli/makeThemeType.js +222 -0
- package/dist/components/badge/Badge.d.ts +7 -0
- package/dist/components/badge/Badge.js +9 -0
- package/dist/components/dialog/ConfirmDialog.d.ts +23 -0
- package/dist/components/dialog/ConfirmDialog.js +57 -0
- package/dist/components/dialog/types.d.ts +20 -0
- package/dist/components/dialog/types.js +1 -0
- package/dist/components/divider/Divider.d.ts +8 -0
- package/dist/components/divider/Divider.js +10 -0
- package/dist/components/fold/Fold.d.ts +3 -0
- package/dist/components/fold/Fold.js +44 -0
- package/dist/components/fold/types.d.ts +28 -0
- package/dist/components/fold/types.js +1 -0
- package/dist/components/form/Field.d.ts +30 -0
- package/dist/components/form/Field.js +59 -0
- package/dist/components/form/Form.d.ts +32 -0
- package/dist/components/form/Form.js +163 -0
- package/dist/components/form/context.d.ts +14 -0
- package/dist/components/form/context.js +20 -0
- package/dist/components/form/types.d.ts +115 -0
- package/dist/components/form/types.js +1 -0
- package/dist/components/key-hint/KeyHint.d.ts +10 -0
- package/dist/components/key-hint/KeyHint.js +12 -0
- package/dist/components/multi-select/MultiSelectInput.d.ts +125 -0
- package/dist/components/multi-select/MultiSelectInput.js +287 -0
- package/dist/components/multi-select/types.d.ts +100 -0
- package/dist/components/multi-select/types.js +1 -0
- package/dist/components/number-input/NumberInput.d.ts +11 -0
- package/dist/components/number-input/NumberInput.js +44 -0
- package/dist/components/progress-bar/ProgressBar.d.ts +11 -0
- package/dist/components/progress-bar/ProgressBar.js +9 -0
- package/dist/components/search-input/SearchInput.d.ts +10 -0
- package/dist/components/search-input/SearchInput.js +15 -0
- package/dist/components/select/SelectInput.d.ts +100 -0
- package/dist/components/select/SelectInput.js +147 -0
- package/dist/components/select/types.d.ts +63 -0
- package/dist/components/select/types.js +1 -0
- package/dist/components/spinner/Spinner.d.ts +12 -0
- package/dist/components/spinner/Spinner.js +24 -0
- package/dist/components/tabs/Tabs.d.ts +3 -0
- package/dist/components/tabs/Tabs.js +57 -0
- package/dist/components/tabs/types.d.ts +32 -0
- package/dist/components/tabs/types.js +1 -0
- package/dist/components/text/TextInput.d.ts +39 -0
- package/dist/components/text/TextInput.js +233 -0
- package/dist/components/text/types.d.ts +62 -0
- package/dist/components/text/types.js +1 -0
- package/dist/dev/dev-screen.d.ts +36 -0
- package/dist/dev/dev-screen.js +142 -0
- package/dist/dev/entrance.d.ts +51 -0
- package/dist/dev/entrance.js +67 -0
- package/dist/dev/globalKey-display.d.ts +2 -0
- package/dist/dev/globalKey-display.js +8 -0
- package/dist/dev/types.d.ts +9 -0
- package/dist/dev/types.js +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +50 -0
- package/dist/keyboard/check-global-key.d.ts +18 -0
- package/dist/keyboard/check-global-key.js +52 -0
- package/dist/keyboard/context.d.ts +398 -0
- package/dist/keyboard/context.js +9 -0
- package/dist/keyboard/global-key-processor/index.d.ts +16 -0
- package/dist/keyboard/global-key-processor/index.js +75 -0
- package/dist/keyboard/global-sequence-processor/index.d.ts +14 -0
- package/dist/keyboard/global-sequence-processor/index.js +165 -0
- package/dist/keyboard/hook.d.ts +51 -0
- package/dist/keyboard/hook.js +105 -0
- package/dist/keyboard/index.d.ts +5 -0
- package/dist/keyboard/index.js +3 -0
- package/dist/keyboard/keyNormalizer.d.ts +37 -0
- package/dist/keyboard/keyNormalizer.js +119 -0
- package/dist/keyboard/layer-handler.d.ts +57 -0
- package/dist/keyboard/layer-handler.js +274 -0
- package/dist/keyboard/modal-processor/index.d.ts +16 -0
- package/dist/keyboard/modal-processor/index.js +97 -0
- package/dist/keyboard/overlay-processor/index.d.ts +16 -0
- package/dist/keyboard/overlay-processor/index.js +30 -0
- package/dist/keyboard/pipeline/chain.d.ts +10 -0
- package/dist/keyboard/pipeline/chain.js +45 -0
- package/dist/keyboard/pipeline/context.d.ts +34 -0
- package/dist/keyboard/pipeline/context.js +42 -0
- package/dist/keyboard/pipeline/index.d.ts +3 -0
- package/dist/keyboard/pipeline/index.js +2 -0
- package/dist/keyboard/provider.d.ts +32 -0
- package/dist/keyboard/provider.js +985 -0
- package/dist/keyboard/screen-stack-processor/index.d.ts +14 -0
- package/dist/keyboard/screen-stack-processor/index.js +32 -0
- package/dist/keyboard/types.d.ts +632 -0
- package/dist/keyboard/types.js +1 -0
- package/dist/language/context.d.ts +7 -0
- package/dist/language/context.js +7 -0
- package/dist/language/hook.d.ts +19 -0
- package/dist/language/hook.js +26 -0
- package/dist/language/index.d.ts +3 -0
- package/dist/language/index.js +2 -0
- package/dist/language/provider.d.ts +30 -0
- package/dist/language/provider.js +155 -0
- package/dist/language/types.d.ts +124 -0
- package/dist/language/types.js +1 -0
- package/dist/screen/ModalContext.d.ts +11 -0
- package/dist/screen/ModalContext.js +12 -0
- package/dist/screen/OverlayContext.d.ts +9 -0
- package/dist/screen/OverlayContext.js +10 -0
- package/dist/screen/context.d.ts +61 -0
- package/dist/screen/context.js +8 -0
- package/dist/screen/current-screen.d.ts +16 -0
- package/dist/screen/current-screen.js +38 -0
- package/dist/screen/hook.d.ts +13 -0
- package/dist/screen/hook.js +20 -0
- package/dist/screen/index.d.ts +7 -0
- package/dist/screen/index.js +5 -0
- package/dist/screen/provider.d.ts +112 -0
- package/dist/screen/provider.js +577 -0
- package/dist/screen/registry.d.ts +27 -0
- package/dist/screen/registry.js +71 -0
- package/dist/screen/types.d.ts +227 -0
- package/dist/screen/types.js +1 -0
- package/dist/storage/index.d.ts +45 -0
- package/dist/storage/index.js +46 -0
- package/dist/storage/storage.d.ts +139 -0
- package/dist/storage/storage.js +236 -0
- package/dist/storage/types.d.ts +145 -0
- package/dist/storage/types.js +1 -0
- package/dist/theme/context.d.ts +7 -0
- package/dist/theme/context.js +7 -0
- package/dist/theme/hook.d.ts +11 -0
- package/dist/theme/hook.js +18 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +2 -0
- package/dist/theme/provider.d.ts +18 -0
- package/dist/theme/provider.js +308 -0
- package/dist/theme/types.d.ts +53 -0
- package/dist/theme/types.js +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { I18nContextValue } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Access the i18n API from within a component tree wrapped by
|
|
4
|
+
* {@link LanguageProvider}.
|
|
5
|
+
*
|
|
6
|
+
* Returns `{ t, setLanguage, getLanguages, mergeLanguage, currentLanguage }`.
|
|
7
|
+
*
|
|
8
|
+
* Must be used inside `<LanguageProvider>`. Throws with a clear error
|
|
9
|
+
* message if no provider is found in the component tree.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* function Greeting() {
|
|
14
|
+
* const { t, setLanguage } = useI18n();
|
|
15
|
+
* return <Text>{t('hello')}</Text>;
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function useI18n(): I18nContextValue;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { LanguageContext } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Access the i18n API from within a component tree wrapped by
|
|
5
|
+
* {@link LanguageProvider}.
|
|
6
|
+
*
|
|
7
|
+
* Returns `{ t, setLanguage, getLanguages, mergeLanguage, currentLanguage }`.
|
|
8
|
+
*
|
|
9
|
+
* Must be used inside `<LanguageProvider>`. Throws with a clear error
|
|
10
|
+
* message if no provider is found in the component tree.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* function Greeting() {
|
|
15
|
+
* const { t, setLanguage } = useI18n();
|
|
16
|
+
* return <Text>{t('hello')}</Text>;
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function useI18n() {
|
|
21
|
+
const ctx = useContext(LanguageContext);
|
|
22
|
+
if (!ctx) {
|
|
23
|
+
throw new Error('[Ink-Cartridge] useI18n() must be called inside a <LanguageProvider>.');
|
|
24
|
+
}
|
|
25
|
+
return ctx;
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
interface LanguageProviderProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
resources?: Record<string, Record<string, string>>;
|
|
5
|
+
path?: string;
|
|
6
|
+
defaultLanguage?: string;
|
|
7
|
+
fallbackLanguage?: string;
|
|
8
|
+
defaultContext?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Context provider for internationalization (i18n).
|
|
12
|
+
*
|
|
13
|
+
* Loads translation resources either from a directory of `{locale}.json` files
|
|
14
|
+
* (via the `path` prop) or from a pre-built inline object (via `resources`).
|
|
15
|
+
*
|
|
16
|
+
* Once mounted, descendant components can use the {@link useI18n} hook to
|
|
17
|
+
* access the `t()` translation function, `setLanguage()` to switch locales,
|
|
18
|
+
* and `getLanguages()` / `currentLanguage` for language discovery.
|
|
19
|
+
*
|
|
20
|
+
* Language switches trigger an immediate re-render of all consumer components.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <LanguageProvider path="./locales" defaultLanguage="en-US">
|
|
25
|
+
* <MyApp />
|
|
26
|
+
* </LanguageProvider>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function LanguageProvider({ children, resources: inlineResources, path, defaultLanguage, fallbackLanguage, defaultContext, }: LanguageProviderProps): React.JSX.Element;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React, { useState, useMemo, useCallback } from 'react';
|
|
2
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { LanguageContext } from './context.js';
|
|
5
|
+
function loadFromPath(dirPath) {
|
|
6
|
+
const resources = {};
|
|
7
|
+
let files;
|
|
8
|
+
try {
|
|
9
|
+
files = readdirSync(dirPath);
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13
|
+
throw new Error(`[Ink-Cartridge] LanguageProvider failed to read directory "${dirPath}": ${msg}`);
|
|
14
|
+
}
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
if (file.endsWith('.json')) {
|
|
17
|
+
const lang = file.replace('.json', '');
|
|
18
|
+
const fullPath = resolve(dirPath, file);
|
|
19
|
+
let raw;
|
|
20
|
+
try {
|
|
21
|
+
raw = readFileSync(fullPath, 'utf-8');
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25
|
+
throw new Error(`[Ink-Cartridge] LanguageProvider failed to read "${fullPath}": ${msg}`);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
resources[lang] = flatJSON(JSON.parse(raw));
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32
|
+
throw new Error(`[Ink-Cartridge] LanguageProvider failed to parse "${file}" as JSON: ${msg}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return resources;
|
|
37
|
+
}
|
|
38
|
+
function flatJSON(obj, prefix = '') {
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
41
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
42
|
+
if (typeof value === 'string') {
|
|
43
|
+
result[fullKey] = value;
|
|
44
|
+
}
|
|
45
|
+
else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
46
|
+
result[fullKey] = String(value);
|
|
47
|
+
}
|
|
48
|
+
else if (Array.isArray(value)) {
|
|
49
|
+
result[fullKey] = value.map((v) => String(v)).join(', ');
|
|
50
|
+
}
|
|
51
|
+
else if (typeof value === 'object' && value !== null) {
|
|
52
|
+
Object.assign(result, flatJSON(value, fullKey));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
function interpolate(template, params) {
|
|
58
|
+
if (!params)
|
|
59
|
+
return template;
|
|
60
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => String(params[key] ?? `{${key}}`));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Context provider for internationalization (i18n).
|
|
64
|
+
*
|
|
65
|
+
* Loads translation resources either from a directory of `{locale}.json` files
|
|
66
|
+
* (via the `path` prop) or from a pre-built inline object (via `resources`).
|
|
67
|
+
*
|
|
68
|
+
* Once mounted, descendant components can use the {@link useI18n} hook to
|
|
69
|
+
* access the `t()` translation function, `setLanguage()` to switch locales,
|
|
70
|
+
* and `getLanguages()` / `currentLanguage` for language discovery.
|
|
71
|
+
*
|
|
72
|
+
* Language switches trigger an immediate re-render of all consumer components.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* <LanguageProvider path="./locales" defaultLanguage="en-US">
|
|
77
|
+
* <MyApp />
|
|
78
|
+
* </LanguageProvider>
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export function LanguageProvider({ children, resources: inlineResources, path, defaultLanguage, fallbackLanguage, defaultContext, }) {
|
|
82
|
+
const rawResources = useMemo(() => {
|
|
83
|
+
if (inlineResources) {
|
|
84
|
+
const flat = {};
|
|
85
|
+
for (const [lang, obj] of Object.entries(inlineResources)) {
|
|
86
|
+
flat[lang] = flatJSON(obj);
|
|
87
|
+
}
|
|
88
|
+
return flat;
|
|
89
|
+
}
|
|
90
|
+
if (path)
|
|
91
|
+
return loadFromPath(path);
|
|
92
|
+
return {};
|
|
93
|
+
}, [inlineResources, path]);
|
|
94
|
+
// Overlay state for mergeLanguage — avoids mutating rawResources so that
|
|
95
|
+
// setLanguage closures always see the latest language list.
|
|
96
|
+
const [mergedResources, setMergedResources] = useState(null);
|
|
97
|
+
const effectiveResources = mergedResources ?? rawResources;
|
|
98
|
+
const [mergeCounter, setMergeCounter] = useState(0);
|
|
99
|
+
const languages = useMemo(() => Object.keys(effectiveResources), [effectiveResources, mergeCounter]);
|
|
100
|
+
const [lang, setLang] = useState(defaultLanguage ?? languages[0] ?? 'en-US');
|
|
101
|
+
const currentResources = effectiveResources[lang] ?? {};
|
|
102
|
+
const [defaultCtx, setDefaultCtx] = useState(defaultContext);
|
|
103
|
+
const t = useCallback((key, options) => {
|
|
104
|
+
const params = options?.params;
|
|
105
|
+
const context = options?.context ?? defaultCtx;
|
|
106
|
+
let value;
|
|
107
|
+
// Context-aware lookup: first try key.<context>
|
|
108
|
+
if (context) {
|
|
109
|
+
const contextKey = `${key}.${context}`;
|
|
110
|
+
value = currentResources[contextKey];
|
|
111
|
+
if (value === undefined && fallbackLanguage) {
|
|
112
|
+
const fb = effectiveResources[fallbackLanguage];
|
|
113
|
+
if (fb)
|
|
114
|
+
value = fb[contextKey];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Fall back to the base key if context lookup failed
|
|
118
|
+
if (value === undefined) {
|
|
119
|
+
value = currentResources[key];
|
|
120
|
+
if (value === undefined && fallbackLanguage) {
|
|
121
|
+
const fb = effectiveResources[fallbackLanguage];
|
|
122
|
+
if (fb)
|
|
123
|
+
value = fb[key];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (value === undefined)
|
|
127
|
+
return key;
|
|
128
|
+
return interpolate(value, params);
|
|
129
|
+
}, [currentResources, effectiveResources, fallbackLanguage, defaultCtx]);
|
|
130
|
+
const setLanguage = useCallback((newLang) => {
|
|
131
|
+
if (effectiveResources[newLang]) {
|
|
132
|
+
setLang(newLang);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
throw new Error(`[Ink-Cartridge] Language "${newLang}" is not available. ` +
|
|
136
|
+
`Available languages: ${languages.join(', ')}`);
|
|
137
|
+
}
|
|
138
|
+
}, [effectiveResources, languages]);
|
|
139
|
+
const mergeLanguage = useCallback((paths) => {
|
|
140
|
+
const base = mergedResources ?? rawResources;
|
|
141
|
+
const merged = { ...base };
|
|
142
|
+
for (const dirPath of paths) {
|
|
143
|
+
const incoming = loadFromPath(dirPath);
|
|
144
|
+
for (const [langCode, flat] of Object.entries(incoming)) {
|
|
145
|
+
merged[langCode] = { ...(merged[langCode] ?? {}), ...flat };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
setMergedResources(merged);
|
|
149
|
+
setMergeCounter((n) => n + 1);
|
|
150
|
+
}, [mergedResources, rawResources]);
|
|
151
|
+
const setDefaultContext = useCallback((ctx) => { setDefaultCtx(ctx); }, []);
|
|
152
|
+
const getLanguages = useCallback(() => Object.keys(effectiveResources), [effectiveResources, mergeCounter]);
|
|
153
|
+
const ctx = useMemo(() => ({ t, setLanguage, setDefaultContext, getLanguages, mergeLanguage, currentLanguage: lang }), [t, setLanguage, getLanguages, mergeLanguage, lang]);
|
|
154
|
+
return React.createElement(LanguageContext.Provider, { value: ctx }, children);
|
|
155
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the {@link LanguageProvider} component.
|
|
4
|
+
*
|
|
5
|
+
* Provide at least one of `resources` (inline translation object) or `path`
|
|
6
|
+
* (directory of `{locale}.json` files).
|
|
7
|
+
*/
|
|
8
|
+
export interface LanguageProviderProps {
|
|
9
|
+
/** Child React tree that receives i18n context. */
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* Inline translation resources keyed by locale code.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* resources={{ 'en-US': { hello: 'Hello' }, 'zh-CN': { hello: '你好' } }}
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
resources?: Record<string, Record<string, string>>;
|
|
20
|
+
/**
|
|
21
|
+
* Path to a directory scanned synchronously for `{locale}.json` files.
|
|
22
|
+
* Each file becomes one language resource.
|
|
23
|
+
*/
|
|
24
|
+
path?: string;
|
|
25
|
+
/** Initial language code. Defaults to the first available locale. */
|
|
26
|
+
defaultLanguage?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Fallback language to try when a key is missing in the current language.
|
|
29
|
+
* If the key is also missing in the fallback, the key itself is returned.
|
|
30
|
+
*/
|
|
31
|
+
fallbackLanguage?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Default context applied to every `t()` call unless an explicit `context`
|
|
34
|
+
* option is passed.
|
|
35
|
+
*
|
|
36
|
+
* When set, `t('greeting')` behaves as if every call were
|
|
37
|
+
* `t('greeting', { context: defaultContext })` — it first tries
|
|
38
|
+
* `key.<defaultContext>`, then falls back to `key`, then to the key itself.
|
|
39
|
+
*
|
|
40
|
+
* Can be changed dynamically via the `setDefaultContext` API returned by
|
|
41
|
+
* `useI18n()`.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* <LanguageProvider defaultContext="male" ...>
|
|
46
|
+
* // t('greeting') → looks up 'greeting.male' first
|
|
47
|
+
* </LanguageProvider>
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
defaultContext?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Value provided by {@link LanguageProvider} via React context.
|
|
54
|
+
* Accessed via the {@link useI18n} hook.
|
|
55
|
+
*/
|
|
56
|
+
export interface I18nContextValue {
|
|
57
|
+
/**
|
|
58
|
+
* Translate a key to the current language.
|
|
59
|
+
*
|
|
60
|
+
* Dot-separated nested keys are resolved via the flat key map
|
|
61
|
+
* (e.g. `menu.title` finds the key `"menu.title"` in the JSON).
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // Simple key
|
|
65
|
+
* t('hello') → 'Hello'
|
|
66
|
+
*
|
|
67
|
+
* // Parameter interpolation
|
|
68
|
+
* t('welcome', { params: { name: 'Alice' } }) → 'Welcome, Alice'
|
|
69
|
+
*
|
|
70
|
+
* // Context-based lookup (e.g. gendered forms)
|
|
71
|
+
* // Resources: { 'greeting': 'Hello', 'greeting.female': 'Hello, madam' }
|
|
72
|
+
* t('greeting', { context: 'female' }) → 'Hello, madam'
|
|
73
|
+
* t('greeting', { context: 'unknown' }) → 'Hello' (fallback)
|
|
74
|
+
*
|
|
75
|
+
* // Context + interpolation
|
|
76
|
+
* // Resources: { 'welcome.female': 'Welcome, Ms. {name}' }
|
|
77
|
+
* t('welcome', { context: 'female', params: { name: 'Alice' } }) → 'Welcome, Ms. Alice'
|
|
78
|
+
*
|
|
79
|
+
* Missing keys resolve to the key string itself.
|
|
80
|
+
*/
|
|
81
|
+
t: (key: string, options?: {
|
|
82
|
+
params?: Record<string, string | number>;
|
|
83
|
+
context?: string;
|
|
84
|
+
}) => string;
|
|
85
|
+
/**
|
|
86
|
+
* Switch to a different language. Triggers re-render of all consumers.
|
|
87
|
+
*
|
|
88
|
+
* @throws {Error} If the requested language is not available, throws
|
|
89
|
+
* with a message listing available languages.
|
|
90
|
+
*/
|
|
91
|
+
setLanguage: (lang: string) => void;
|
|
92
|
+
/** Returns all available locale codes. */
|
|
93
|
+
getLanguages: () => string[];
|
|
94
|
+
/**
|
|
95
|
+
* Merge translation files from one or more directory paths into the
|
|
96
|
+
* current resources. Later paths override earlier paths when the same
|
|
97
|
+
* key exists in multiple sources. Triggers re-render of all consumers.
|
|
98
|
+
*
|
|
99
|
+
* Only merges languages whose JSON files exist in the provided paths;
|
|
100
|
+
* other languages are unaffected.
|
|
101
|
+
*
|
|
102
|
+
* @param paths Array of directory paths, each containing `{locale}.json` files.
|
|
103
|
+
* Applied in order: later paths win on key conflicts.
|
|
104
|
+
*/
|
|
105
|
+
mergeLanguage: (paths: string[]) => void;
|
|
106
|
+
/**
|
|
107
|
+
* Dynamically update the default context for all `t()` calls.
|
|
108
|
+
*
|
|
109
|
+
* Pass `undefined` to clear the default context, restoring the original
|
|
110
|
+
* behaviour where only the bare key is looked up.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```tsx
|
|
114
|
+
* setDefaultContext('female');
|
|
115
|
+
* // All subsequent t() calls now prefer key.<female> first.
|
|
116
|
+
*
|
|
117
|
+
* setDefaultContext(undefined);
|
|
118
|
+
* // Back to bare-key-only lookup.
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
setDefaultContext: (context?: string) => void;
|
|
122
|
+
/** Currently active locale code. */
|
|
123
|
+
currentLanguage: string;
|
|
124
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context that provides the modal ID when rendering inside
|
|
3
|
+
* a modal component. Null when rendering a regular screen.
|
|
4
|
+
*
|
|
5
|
+
* Used by the keyboard system to isolate per-modal keyboard layers
|
|
6
|
+
* by modal ID instead of component type (enabling multiple instances
|
|
7
|
+
* of the same component as different modals).
|
|
8
|
+
*
|
|
9
|
+
* Architecturally symmetric to {@link OverlayContext}.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ModalContext: import("react").Context<string | null>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Context that provides the modal ID when rendering inside
|
|
4
|
+
* a modal component. Null when rendering a regular screen.
|
|
5
|
+
*
|
|
6
|
+
* Used by the keyboard system to isolate per-modal keyboard layers
|
|
7
|
+
* by modal ID instead of component type (enabling multiple instances
|
|
8
|
+
* of the same component as different modals).
|
|
9
|
+
*
|
|
10
|
+
* Architecturally symmetric to {@link OverlayContext}.
|
|
11
|
+
*/
|
|
12
|
+
export const ModalContext = createContext(null);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context that provides the overlay ID when rendering inside
|
|
3
|
+
* an overlay component. Null when rendering a regular screen.
|
|
4
|
+
*
|
|
5
|
+
* Used by the keyboard system to isolate per-overlay keyboard layers
|
|
6
|
+
* by overlay ID instead of component type (enabling multiple instances
|
|
7
|
+
* of the same component as different overlays).
|
|
8
|
+
*/
|
|
9
|
+
export declare const OverlayContext: import("react").Context<string | null>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Context that provides the overlay ID when rendering inside
|
|
4
|
+
* an overlay component. Null when rendering a regular screen.
|
|
5
|
+
*
|
|
6
|
+
* Used by the keyboard system to isolate per-overlay keyboard layers
|
|
7
|
+
* by overlay ID instead of component type (enabling multiple instances
|
|
8
|
+
* of the same component as different overlays).
|
|
9
|
+
*/
|
|
10
|
+
export const OverlayContext = createContext(null);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import type { SkipFn, BackFn, GotoScreenFn, OpenOverlayFn, CloseOverlayFn, CloseAllOverlaysFn, ActivateOverlayFn, DeactivateOverlayFn, OpenModalFn, CloseModalFn, CloseAllModalsFn, OverlayEntry, ModalEntry } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Value provided by {@link ScenarioManagementProvider} via React context.
|
|
5
|
+
*
|
|
6
|
+
* Includes the current screen, all active overlays, navigation functions,
|
|
7
|
+
* overlay management functions, and modal management functions.
|
|
8
|
+
*/
|
|
9
|
+
export interface ScreenSystemContextValue {
|
|
10
|
+
/** The rendered React element for the current (top-of-stack) screen. */
|
|
11
|
+
currentScreen: ReactNode;
|
|
12
|
+
/** Rendered React elements for all overlays, sorted by zIndex ascending. */
|
|
13
|
+
currentOverlays: ReactNode[];
|
|
14
|
+
/** Rendered React elements for all modals, sorted by zIndex ascending. */
|
|
15
|
+
currentModals: ReactNode[];
|
|
16
|
+
/** Full navigation path from root to the current screen. */
|
|
17
|
+
currentPath: React.ComponentType<any>[];
|
|
18
|
+
/** Navigate down the tree to a direct child of the current screen. */
|
|
19
|
+
skip: SkipFn;
|
|
20
|
+
/** Navigate up the tree toward the root. */
|
|
21
|
+
back: BackFn;
|
|
22
|
+
/** Jump to any registered screen across branches via LCA resolution. */
|
|
23
|
+
gotoScreen: GotoScreenFn;
|
|
24
|
+
/** Open a new overlay with a unique ID. Multiple overlays can coexist. */
|
|
25
|
+
openOverlay: OpenOverlayFn;
|
|
26
|
+
/** Close a specific overlay by its ID. */
|
|
27
|
+
closeOverlay: CloseOverlayFn;
|
|
28
|
+
/** Close all open overlays at once. */
|
|
29
|
+
closeAllOverlays: CloseAllOverlaysFn;
|
|
30
|
+
/** Activate an overlay so it receives keyboard events. */
|
|
31
|
+
activateOverlay: ActivateOverlayFn;
|
|
32
|
+
/** Deactivate an overlay so it stops receiving keyboard events. */
|
|
33
|
+
deactivateOverlay: DeactivateOverlayFn;
|
|
34
|
+
/** IDs of overlays that are currently active (receiving keyboard events). */
|
|
35
|
+
activeOverlayIds: string[];
|
|
36
|
+
/** All currently displayed overlays with metadata (id, zIndex, etc.). */
|
|
37
|
+
displayedOverlays: OverlayEntry[];
|
|
38
|
+
/** All open modals with metadata (id, zIndex, etc.), sorted by zIndex ascending. */
|
|
39
|
+
displayedModals: ModalEntry[];
|
|
40
|
+
/** The modal entries that correspond to the rendered modal nodes (active + renderNow). Sorted by zIndex ascending. */
|
|
41
|
+
renderedModalEntries: ModalEntry[];
|
|
42
|
+
/** ID of the currently active modal (zIndex highest), or null if none. */
|
|
43
|
+
activeModalId: string | null;
|
|
44
|
+
/** The currently active modal entry (zIndex highest), or null if none. */
|
|
45
|
+
activeModal: ModalEntry | null;
|
|
46
|
+
/** All open modals sorted by zIndex ascending. */
|
|
47
|
+
modalQueue: ModalEntry[];
|
|
48
|
+
/** Open a new modal with a unique ID. */
|
|
49
|
+
openModal: OpenModalFn;
|
|
50
|
+
/** Close a specific modal by its ID. */
|
|
51
|
+
closeModal: CloseModalFn;
|
|
52
|
+
/** Close all open modals at once. */
|
|
53
|
+
closeAllModals: CloseAllModalsFn;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* React context for the screen navigation system.
|
|
57
|
+
*
|
|
58
|
+
* Accessed via {@link useScreenSystem}. Must be provided by a
|
|
59
|
+
* {@link ScenarioManagementProvider} at the root of the component tree.
|
|
60
|
+
*/
|
|
61
|
+
export declare const ScreenSystemContext: import("react").Context<ScreenSystemContextValue | null>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* React context for the screen navigation system.
|
|
4
|
+
*
|
|
5
|
+
* Accessed via {@link useScreenSystem}. Must be provided by a
|
|
6
|
+
* {@link ScenarioManagementProvider} at the root of the component tree.
|
|
7
|
+
*/
|
|
8
|
+
export const ScreenSystemContext = createContext(null);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Render the current screen, overlays, and modals.
|
|
4
|
+
*
|
|
5
|
+
* Multiple overlays are rendered in zIndex order (ascending) so higher
|
|
6
|
+
* zIndex overlays appear on top. Each overlay is wrapped in an
|
|
7
|
+
* OverlayContext.Provider so the keyboard system can isolate per-overlay
|
|
8
|
+
* keyboard layers by overlay ID.
|
|
9
|
+
*
|
|
10
|
+
* Modals are rendered after overlays so they always appear visually on top.
|
|
11
|
+
* Each modal is wrapped in a ModalContext.Provider so the keyboard system
|
|
12
|
+
* can isolate per-modal keyboard layers by modal ID.
|
|
13
|
+
*
|
|
14
|
+
* Architecturally symmetric between overlays and modals.
|
|
15
|
+
*/
|
|
16
|
+
export declare function CurrentScreen(): React.ReactNode;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { useScreenSystem } from './hook.js';
|
|
4
|
+
import { OverlayContext } from './OverlayContext.js';
|
|
5
|
+
import { ModalContext } from './ModalContext.js';
|
|
6
|
+
/**
|
|
7
|
+
* Render the current screen, overlays, and modals.
|
|
8
|
+
*
|
|
9
|
+
* Multiple overlays are rendered in zIndex order (ascending) so higher
|
|
10
|
+
* zIndex overlays appear on top. Each overlay is wrapped in an
|
|
11
|
+
* OverlayContext.Provider so the keyboard system can isolate per-overlay
|
|
12
|
+
* keyboard layers by overlay ID.
|
|
13
|
+
*
|
|
14
|
+
* Modals are rendered after overlays so they always appear visually on top.
|
|
15
|
+
* Each modal is wrapped in a ModalContext.Provider so the keyboard system
|
|
16
|
+
* can isolate per-modal keyboard layers by modal ID.
|
|
17
|
+
*
|
|
18
|
+
* Architecturally symmetric between overlays and modals.
|
|
19
|
+
*/
|
|
20
|
+
export function CurrentScreen() {
|
|
21
|
+
const { currentScreen, currentOverlays, displayedOverlays, currentModals, renderedModalEntries } = useScreenSystem();
|
|
22
|
+
// Build overlay elements with OverlayContext wrappers
|
|
23
|
+
const wrappedOverlays = currentOverlays.map((overlayNode, i) => {
|
|
24
|
+
const entry = displayedOverlays[i];
|
|
25
|
+
if (!entry)
|
|
26
|
+
return overlayNode;
|
|
27
|
+
return React.createElement(OverlayContext.Provider, { value: entry.id, key: `ovl-ctx-${entry.id}` }, overlayNode);
|
|
28
|
+
});
|
|
29
|
+
// Build modal elements with ModalContext wrappers (symmetric to overlays).
|
|
30
|
+
// Uses renderedModalEntries (parallel to currentModals) for correct ID matching.
|
|
31
|
+
const wrappedModals = currentModals.map((modalNode, i) => {
|
|
32
|
+
const entry = renderedModalEntries[i];
|
|
33
|
+
if (!entry)
|
|
34
|
+
return modalNode;
|
|
35
|
+
return React.createElement(ModalContext.Provider, { value: entry.id, key: `mdl-ctx-${entry.id}` }, modalNode);
|
|
36
|
+
});
|
|
37
|
+
return React.createElement(Box, { flexDirection: 'column', width: '100%', height: '100%' }, currentScreen, ...wrappedOverlays.map((w) => w), ...wrappedModals.map((w) => w));
|
|
38
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ScreenSystemContextValue } from './context.js';
|
|
2
|
+
/**
|
|
3
|
+
* Access the screen-management API from within a React component.
|
|
4
|
+
*
|
|
5
|
+
* Returns `{ currentScreen, currentOverlays, currentPath, skip, back,
|
|
6
|
+
* gotoScreen, openOverlay, closeOverlay, closeAllOverlays, activateOverlay,
|
|
7
|
+
* deactivateOverlay, activeOverlayIds, displayedOverlays }`.
|
|
8
|
+
*
|
|
9
|
+
* Must be used inside a {@link ScenarioManagementProvider}.
|
|
10
|
+
*
|
|
11
|
+
* @throws If no provider is found in the component tree.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useScreenSystem(): ScreenSystemContextValue;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { ScreenSystemContext } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Access the screen-management API from within a React component.
|
|
5
|
+
*
|
|
6
|
+
* Returns `{ currentScreen, currentOverlays, currentPath, skip, back,
|
|
7
|
+
* gotoScreen, openOverlay, closeOverlay, closeAllOverlays, activateOverlay,
|
|
8
|
+
* deactivateOverlay, activeOverlayIds, displayedOverlays }`.
|
|
9
|
+
*
|
|
10
|
+
* Must be used inside a {@link ScenarioManagementProvider}.
|
|
11
|
+
*
|
|
12
|
+
* @throws If no provider is found in the component tree.
|
|
13
|
+
*/
|
|
14
|
+
export function useScreenSystem() {
|
|
15
|
+
const ctx = useContext(ScreenSystemContext);
|
|
16
|
+
if (!ctx) {
|
|
17
|
+
throw new Error('[Ink-Cartridge] useScreenSystem() must be called inside a <ScenarioManagementProvider>.');
|
|
18
|
+
}
|
|
19
|
+
return ctx;
|
|
20
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { registerComponent } from './registry.js';
|
|
2
|
+
export { ScenarioManagementProvider, skip, back, gotoScreen, openOverlay, closeOverlay, closeAllOverlays, activateOverlay, deactivateOverlay, openModal, closeModal, closeAllModals, clearDispatchers, } from './provider.js';
|
|
3
|
+
export type { ScenarioManagementProviderProps } from './provider.js';
|
|
4
|
+
export { useScreenSystem } from './hook.js';
|
|
5
|
+
export { CurrentScreen } from './current-screen.js';
|
|
6
|
+
export { ModalContext } from './ModalContext.js';
|
|
7
|
+
export type { SkipOptions, SkipFn, BackFn, GotoScreenFn, OpenOverlayFn, CloseOverlayFn, CloseAllOverlaysFn, ActivateOverlayFn, DeactivateOverlayFn, OpenModalFn, CloseModalFn, CloseAllModalsFn, OpenOverlayOptions, OpenModalOptions, OverlayEntry, ModalEntry, RegisterOptions, } from './types.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { registerComponent } from './registry.js';
|
|
2
|
+
export { ScenarioManagementProvider, skip, back, gotoScreen, openOverlay, closeOverlay, closeAllOverlays, activateOverlay, deactivateOverlay, openModal, closeModal, closeAllModals, clearDispatchers, } from './provider.js';
|
|
3
|
+
export { useScreenSystem } from './hook.js';
|
|
4
|
+
export { CurrentScreen } from './current-screen.js';
|
|
5
|
+
export { ModalContext } from './ModalContext.js';
|