@cplieger/web-terminal-ui 2.1.1 → 2.1.2
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/package.json +1 -1
- package/src/composition.ts +19 -4
- package/src/mount.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Reference touch-first browser UI for @cplieger/web-terminal-engine: textarea input model, mobile key toolbar, context menu, IME, predictive echo, viewport/keyboard-inset handling, and CSS. One mount() call.",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
package/src/composition.ts
CHANGED
|
@@ -32,6 +32,7 @@ const { bracketTextForPaste, prepareTextForTerminal } = keyboard;
|
|
|
32
32
|
|
|
33
33
|
let textarea: HTMLTextAreaElement;
|
|
34
34
|
let compositionView: HTMLElement;
|
|
35
|
+
let scrollEl: HTMLElement;
|
|
35
36
|
let getCursorPx: () => { left: number; top: number; cellH: number };
|
|
36
37
|
let send: (bytes: string) => void;
|
|
37
38
|
|
|
@@ -43,11 +44,13 @@ let compositionSuffix = "";
|
|
|
43
44
|
export function init(opts: {
|
|
44
45
|
textarea: HTMLTextAreaElement;
|
|
45
46
|
compositionView: HTMLElement;
|
|
47
|
+
scrollEl: HTMLElement;
|
|
46
48
|
getCursorPx: () => { left: number; top: number; cellH: number };
|
|
47
49
|
send: (bytes: string) => void;
|
|
48
50
|
}): void {
|
|
49
51
|
textarea = opts.textarea;
|
|
50
52
|
compositionView = opts.compositionView;
|
|
53
|
+
scrollEl = opts.scrollEl;
|
|
51
54
|
getCursorPx = opts.getCursorPx;
|
|
52
55
|
send = opts.send;
|
|
53
56
|
|
|
@@ -137,13 +140,25 @@ function onPaste(ev: ClipboardEvent): void {
|
|
|
137
140
|
* the focused textarea is on screen at a sane location. */
|
|
138
141
|
export function positionCompositionView(): void {
|
|
139
142
|
const { left, top, cellH } = getCursorPx();
|
|
143
|
+
// getCursorPx() returns the cursor position in the scroll container's CONTENT
|
|
144
|
+
// coordinate space (measured from the top of all rows, scrollback included).
|
|
145
|
+
// The composition view is position:absolute inside that scroll container, so
|
|
146
|
+
// it scrolls with the content — content coordinates place it at the visible
|
|
147
|
+
// cursor directly.
|
|
140
148
|
compositionView.style.left = `${left}px`;
|
|
141
149
|
compositionView.style.top = `${top}px`;
|
|
142
150
|
compositionView.style.height = `${cellH}px`;
|
|
143
151
|
compositionView.style.lineHeight = `${cellH}px`;
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
// The textarea is position:fixed (viewport space) so iOS reliably opens the
|
|
153
|
+
// soft keyboard for it. A fixed element does NOT scroll with the content, so
|
|
154
|
+
// the content-space cursor offset must be converted to viewport space by
|
|
155
|
+
// subtracting the scroll offset. Without this, once a screenful of output has
|
|
156
|
+
// scrolled the cursor's content offset well below the viewport, the fixed
|
|
157
|
+
// textarea lands far off-screen; focusing it makes iOS scroll the page up to
|
|
158
|
+
// pull the focused input into view, pushing the terminal output and prompt
|
|
159
|
+
// behind the keyboard until the user scrolls back. Anchoring the textarea at
|
|
160
|
+
// the VISIBLE cursor keeps it on-screen so iOS has no reason to scroll.
|
|
161
|
+
textarea.style.left = `${left - scrollEl.scrollLeft}px`;
|
|
162
|
+
textarea.style.top = `${top - scrollEl.scrollTop}px`;
|
|
148
163
|
textarea.style.height = `${cellH}px`;
|
|
149
164
|
}
|
package/src/mount.ts
CHANGED