ink-cartridge 3.6.1 → 3.7.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/dev/dev-screen.js
CHANGED
|
@@ -5,6 +5,7 @@ import { useKeyboard, useModalMissListener } from "../keyboard/hook.js";
|
|
|
5
5
|
import { ModalContext } from "../screen/ModalContext.js";
|
|
6
6
|
import { registerComponent } from "../screen/registry.js";
|
|
7
7
|
import { closeDevTool } from "./entrance.js";
|
|
8
|
+
import GlobalKeyDisplayBox from "./globalKey-display.js";
|
|
8
9
|
const PANEL_HEIGHT = 30;
|
|
9
10
|
/**
|
|
10
11
|
* Developer debugging modal for the ink-cartridge screen system.
|
|
@@ -44,6 +45,7 @@ export function DevScreen({ top: initialTop, left }) {
|
|
|
44
45
|
const { boundKeyboard } = useKeyboard();
|
|
45
46
|
const modalId = useContext(ModalContext);
|
|
46
47
|
const { rows } = useWindowSize();
|
|
48
|
+
const { openModal } = useScreenSystem();
|
|
47
49
|
const [offsetTop, setOffsetTop] = useState(initialTop);
|
|
48
50
|
const [flashBorder, setFlashBorder] = useState(false);
|
|
49
51
|
const flashTimerRef = useRef(null);
|
|
@@ -76,10 +78,21 @@ export function DevScreen({ top: initialTop, left }) {
|
|
|
76
78
|
if (modalId)
|
|
77
79
|
closeDevTool();
|
|
78
80
|
});
|
|
81
|
+
const u4 = boundKeyboard(['ctrl+g'], () => {
|
|
82
|
+
openModal('__global-display__', GlobalKeyDisplayBox, {
|
|
83
|
+
top: initialTop + 3,
|
|
84
|
+
// Add three to prevent complete coverage of the DevBox from fragmenting the visual experience
|
|
85
|
+
left: left
|
|
86
|
+
});
|
|
87
|
+
// Actually, we don't need to pass in zindex here.
|
|
88
|
+
// Because the current mechanism is that when no zindex is passed, the following modal box overwrites the preceding modal box by default
|
|
89
|
+
// And our automatic activation mechanism ensures that it is also the one in the highest mode box.
|
|
90
|
+
});
|
|
79
91
|
return () => {
|
|
80
92
|
u1();
|
|
81
93
|
u2();
|
|
82
94
|
u3();
|
|
95
|
+
u4();
|
|
83
96
|
};
|
|
84
97
|
}, [modalId]);
|
|
85
98
|
// When the terminal is resized the current position may land outside
|
|
@@ -132,7 +145,7 @@ export function DevScreen({ top: initialTop, left }) {
|
|
|
132
145
|
React.createElement(Text, { dimColor: true },
|
|
133
146
|
"Screens: ",
|
|
134
147
|
currentPath.length,
|
|
135
|
-
" | \u2191\u2193 Move | Esc
|
|
148
|
+
" | \u2191\u2193 Move | Esc CloseI | Ctrl + G GlobalKeys Display"),
|
|
136
149
|
React.createElement(Text, { dimColor: true },
|
|
137
150
|
"Top: ",
|
|
138
151
|
offsetTop,
|
package/dist/dev/entrance.d.ts
CHANGED
|
@@ -32,9 +32,7 @@ import { DevProps } from "./types.js";
|
|
|
32
32
|
* });
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
|
-
export declare function openDevTool({ top, left, zindex }: DevProps
|
|
36
|
-
zindex?: number;
|
|
37
|
-
}): void;
|
|
35
|
+
export declare function openDevTool({ top, left, zindex }: DevProps): void;
|
|
38
36
|
/**
|
|
39
37
|
* Close the developer debugging modal.
|
|
40
38
|
*
|
|
@@ -1,8 +1,147 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import React, { useContext, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Box, Text, useWindowSize } from "ink";
|
|
3
|
+
import { useKeyboard } from "../keyboard/hook.js";
|
|
4
|
+
import { ModalContext } from "../screen/ModalContext.js";
|
|
5
|
+
import { useScreenSystem } from "../screen/hook.js";
|
|
6
|
+
import { registerComponent } from "../screen/registry.js";
|
|
7
|
+
import { SelectInput } from "../components/select/SelectInput.js";
|
|
8
|
+
// This modal box displays information about all registered global keys.
|
|
9
|
+
// Because it is a modal, its keyboard is independent and will not be
|
|
10
|
+
// affected by DevTool. When the user presses Enter on a list item the
|
|
11
|
+
// detail panel expands; Enter again collapses back to the list.
|
|
12
|
+
// @2026-07-16 v3.6.2
|
|
13
|
+
const PANEL_HEIGHT = 30;
|
|
14
|
+
/**
|
|
15
|
+
* Build a compact label for a global key entry.
|
|
16
|
+
*
|
|
17
|
+
* Shows the key name(s) followed by short badges for non-default options:
|
|
18
|
+
* `[ao]` = affectOverlay, `[xno]` = executeWhenNoOverlay, `[×N]` = times.
|
|
19
|
+
*/
|
|
20
|
+
function buildLabel(entry) {
|
|
21
|
+
const keys = Array.isArray(entry.key) ? entry.key.join(', ') : entry.key;
|
|
22
|
+
const badges = [];
|
|
23
|
+
if (entry.affectOverlay)
|
|
24
|
+
badges.push('[ao]');
|
|
25
|
+
if (entry.executeWhenNoOverlay)
|
|
26
|
+
badges.push('[xno]');
|
|
27
|
+
if (entry.times !== undefined)
|
|
28
|
+
badges.push(`[×${entry.times}]`);
|
|
29
|
+
return badges.length > 0 ? `${keys} ${badges.join(' ')}` : keys;
|
|
8
30
|
}
|
|
31
|
+
function fmtCategory(category) {
|
|
32
|
+
if (category === undefined || category === '*')
|
|
33
|
+
return '*';
|
|
34
|
+
return category.map(c => c.displayName || c.name || '?').join(', ');
|
|
35
|
+
}
|
|
36
|
+
/** Renders a boolean value in green (true) or red (false). */
|
|
37
|
+
function BoolVal({ v }) {
|
|
38
|
+
const c = v === false ? 'red' : 'green';
|
|
39
|
+
return React.createElement(Text, { color: c }, String(v ?? true));
|
|
40
|
+
}
|
|
41
|
+
/** Two-column detail row: dim label on left, value slot on right. */
|
|
42
|
+
function Row({ label, children }) {
|
|
43
|
+
return (React.createElement(Box, { flexDirection: "row" },
|
|
44
|
+
React.createElement(Box, { width: 18 },
|
|
45
|
+
React.createElement(Text, { dimColor: true }, label)),
|
|
46
|
+
children));
|
|
47
|
+
}
|
|
48
|
+
/** Blue separator line for visual sectioning. */
|
|
49
|
+
const Sep = () => (React.createElement(Box, null,
|
|
50
|
+
React.createElement(Text, { color: "blue", dimColor: true }, "─".repeat(50))));
|
|
51
|
+
export default function GlobalKeyDisplayBox({ top: initialTop, left }) {
|
|
52
|
+
const { getGlobalKeys, boundKeyboard } = useKeyboard();
|
|
53
|
+
const { closeModal } = useScreenSystem();
|
|
54
|
+
const modalId = useContext(ModalContext);
|
|
55
|
+
const { rows } = useWindowSize();
|
|
56
|
+
const [offsetTop, setOffsetTop] = useState(initialTop);
|
|
57
|
+
const [expandedEntry, setExpandedEntry] = useState(null);
|
|
58
|
+
const clampTopRef = useRef((next) => next);
|
|
59
|
+
clampTopRef.current = (next) => Math.max(0, Math.min(next, rows - PANEL_HEIGHT));
|
|
60
|
+
const expandedEntryRef = useRef(expandedEntry);
|
|
61
|
+
expandedEntryRef.current = expandedEntry;
|
|
62
|
+
// Bind return at the screen level so Enter collapses the detail panel.
|
|
63
|
+
// When the SelectInput is rendered (not expanded), its focus-level
|
|
64
|
+
// return binding takes priority and this binding is never reached.
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const unReturn = boundKeyboard(['return'], () => {
|
|
67
|
+
if (expandedEntryRef.current) {
|
|
68
|
+
setExpandedEntry(null);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return () => { unReturn(); };
|
|
72
|
+
}, []);
|
|
73
|
+
// Panel movement via up/down arrow keys
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const uUp = boundKeyboard(['up'], () => setOffsetTop(prev => clampTopRef.current(prev - 1)), {
|
|
76
|
+
focusId: 'globalKey-control'
|
|
77
|
+
});
|
|
78
|
+
const uDown = boundKeyboard(['down'], () => setOffsetTop(prev => clampTopRef.current(prev + 1)), {
|
|
79
|
+
focusId: 'globalKey-control'
|
|
80
|
+
});
|
|
81
|
+
return () => { uUp(); uDown(); };
|
|
82
|
+
}, []);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const unEscape = boundKeyboard(['escape'], () => {
|
|
85
|
+
if (modalId)
|
|
86
|
+
closeModal(modalId);
|
|
87
|
+
});
|
|
88
|
+
return () => { unEscape(); };
|
|
89
|
+
}, [modalId]);
|
|
90
|
+
// Re-clamp on terminal resize
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
setOffsetTop(prev => clampTopRef.current(prev));
|
|
93
|
+
}, [rows]);
|
|
94
|
+
const entries = getGlobalKeys();
|
|
95
|
+
const items = entries.map((entry, i) => ({
|
|
96
|
+
label: buildLabel(entry),
|
|
97
|
+
value: entry,
|
|
98
|
+
Key: `gk-${i}`,
|
|
99
|
+
}));
|
|
100
|
+
const handleSelect = (item) => {
|
|
101
|
+
if (expandedEntry === item.value) {
|
|
102
|
+
setExpandedEntry(null);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
setExpandedEntry(item.value);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return (React.createElement(Box, { position: "absolute", top: offsetTop, left: left, height: PANEL_HEIGHT, width: '100%', borderStyle: 'bold', borderColor: 'white', backgroundColor: 'black', flexDirection: "column", paddingX: 1, paddingY: 1 },
|
|
109
|
+
React.createElement(Box, null,
|
|
110
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "\u258C Global Keys "),
|
|
111
|
+
React.createElement(Text, { dimColor: true },
|
|
112
|
+
"(",
|
|
113
|
+
entries.length,
|
|
114
|
+
")")),
|
|
115
|
+
React.createElement(Sep, null),
|
|
116
|
+
expandedEntry ? (React.createElement(Box, { flexDirection: "column", paddingY: 1 },
|
|
117
|
+
React.createElement(Box, { paddingBottom: 1 },
|
|
118
|
+
React.createElement(Text, { color: "yellow", bold: true }, Array.isArray(expandedEntry.key) ? expandedEntry.key.join(', ') : expandedEntry.key)),
|
|
119
|
+
React.createElement(Sep, null),
|
|
120
|
+
React.createElement(Box, { paddingY: 1, flexDirection: "column" },
|
|
121
|
+
React.createElement(Row, { label: "Cover" },
|
|
122
|
+
React.createElement(BoolVal, { v: expandedEntry.cover })),
|
|
123
|
+
React.createElement(Row, { label: "AffectOverlay" },
|
|
124
|
+
React.createElement(BoolVal, { v: expandedEntry.affectOverlay })),
|
|
125
|
+
React.createElement(Row, { label: "ExecWhenNoOv" },
|
|
126
|
+
React.createElement(BoolVal, { v: expandedEntry.executeWhenNoOverlay })),
|
|
127
|
+
React.createElement(Row, { label: "Times" },
|
|
128
|
+
React.createElement(Text, { color: "white" }, expandedEntry.times ?? '—')),
|
|
129
|
+
React.createElement(Row, { label: "Category" },
|
|
130
|
+
React.createElement(Text, { color: "white" }, fmtCategory(expandedEntry.category))),
|
|
131
|
+
React.createElement(Row, { label: "When" },
|
|
132
|
+
React.createElement(BoolVal, { v: expandedEntry.when != null })),
|
|
133
|
+
React.createElement(Row, { label: "Observer" },
|
|
134
|
+
React.createElement(BoolVal, { v: expandedEntry.observer != null }))))) : items.length === 0 ? (React.createElement(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center" },
|
|
135
|
+
React.createElement(Text, { color: "gray" }, "No global keys registered."))) : (React.createElement(SelectInput, { items: items, onSelect: handleSelect, focusId: "global-key-list", limit: 9 })),
|
|
136
|
+
React.createElement(Sep, null),
|
|
137
|
+
React.createElement(Box, { paddingTop: 1 },
|
|
138
|
+
React.createElement(Text, { dimColor: true }, expandedEntry
|
|
139
|
+
? 'Enter: collapse | Esc: close'
|
|
140
|
+
: 'Tab: switch focus | Enter: details | Esc: close')),
|
|
141
|
+
!expandedEntry && (React.createElement(Box, null,
|
|
142
|
+
React.createElement(Text, { dimColor: true }, "After collapsing from detail, press Tab to restore the selection highlight.")))));
|
|
143
|
+
}
|
|
144
|
+
registerComponent(GlobalKeyDisplayBox, {
|
|
145
|
+
top: 0,
|
|
146
|
+
left: 0
|
|
147
|
+
});
|
package/dist/dev/types.d.ts
CHANGED
|
@@ -6,4 +6,18 @@ export interface DevProps {
|
|
|
6
6
|
top: number;
|
|
7
7
|
/** Horizontal position in columns — 0 is the left edge. */
|
|
8
8
|
left: number;
|
|
9
|
+
zindex?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Props accepted by the {@link GlobalKeyDisplayBox} global-keys inspector modal.
|
|
13
|
+
*
|
|
14
|
+
* Opened via `Ctrl+G` while the {@link DevScreen} modal is active. Renders as
|
|
15
|
+
* a white-bordered panel listing all registered global key bindings with
|
|
16
|
+
* expandable detail cards.
|
|
17
|
+
*/
|
|
18
|
+
export interface GlobalProps {
|
|
19
|
+
/** Vertical position in rows — 0 is the top of the terminal. */
|
|
20
|
+
top: number;
|
|
21
|
+
/** Horizontal position in columns — 0 is the left edge. */
|
|
22
|
+
left: number;
|
|
9
23
|
}
|