@cplieger/web-terminal-ui 3.0.0 → 3.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-ui",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "type": "module",
5
5
  "description": "Reference touch-first browser UI for @cplieger/web-terminal-engine: a composable kernel + feature modules (tabs, activity monitor, mobile toolbar, context menu, IME, predictive echo, viewport handling) and CSS. One createTerminal() call.",
6
6
  "license": "GPL-3.0-or-later",
@@ -18,6 +18,10 @@ import type { ClipboardApi } from "./clipboard.js";
18
18
  const LONG_PRESS_MS = 500;
19
19
  const LONG_PRESS_MOVE_PX = 10;
20
20
  const EDGE_MARGIN = 8;
21
+ // When the menu can't fit below the anchor (a long-press near the bottom edge or
22
+ // above the keyboard), it opens above the point. This gap lifts it clear of the
23
+ // fingertip so the touch does not cover the menu items.
24
+ const MENU_GAP = 16;
21
25
 
22
26
  export interface ContextMenuOptions {
23
27
  /** The clipboard feature value, so the menu can offer Copy/Paste through its
@@ -91,16 +95,33 @@ export function contextMenu(opts: ContextMenuOptions = {}): TerminalFeature {
91
95
  if (menu.childElementCount === 0) {
92
96
  return; // nothing to offer
93
97
  }
94
- // Make visible (so it has measurable size) then clamp inside the
95
- // viewport. position:fixed => x/y are viewport coordinates.
98
+ // Make visible (so it has measurable size), then place it within the
99
+ // VISIBLE viewport. position:fixed => x/y are viewport coordinates; the
100
+ // visual viewport (when present) is the area above the on-screen
101
+ // keyboard, so clamping to it keeps the menu off the keyboard too.
96
102
  menu.classList.add("visible");
103
+ const vv = window.visualViewport;
104
+ const viewLeft = vv ? vv.offsetLeft : 0;
105
+ const viewTop = vv ? vv.offsetTop : 0;
106
+ const viewWidth = vv ? vv.width : window.innerWidth;
107
+ const viewHeight = vv ? vv.height : window.innerHeight;
108
+ const menuW = menu.offsetWidth;
109
+ const menuH = menu.offsetHeight;
97
110
  const left = Math.max(
98
- EDGE_MARGIN,
99
- Math.min(x, window.innerWidth - menu.offsetWidth - EDGE_MARGIN),
111
+ viewLeft + EDGE_MARGIN,
112
+ Math.min(x, viewLeft + viewWidth - menuW - EDGE_MARGIN),
100
113
  );
101
- const top = Math.max(
102
- EDGE_MARGIN,
103
- Math.min(y, window.innerHeight - menu.offsetHeight - EDGE_MARGIN),
114
+ // Open just below the point; but if that would overflow the visible
115
+ // bottom (a long-press near the bottom edge, where the user naturally
116
+ // taps), flip above the finger so the menu is neither clipped off-screen
117
+ // nor hidden under the fingertip.
118
+ let top = y;
119
+ if (y + menuH + EDGE_MARGIN > viewTop + viewHeight) {
120
+ top = y - menuH - MENU_GAP;
121
+ }
122
+ top = Math.max(
123
+ viewTop + EDGE_MARGIN,
124
+ Math.min(top, viewTop + viewHeight - menuH - EDGE_MARGIN),
104
125
  );
105
126
  menu.style.left = `${String(left)}px`;
106
127
  menu.style.top = `${String(top)}px`;