Skip to content

Commit 1960f1a

Browse files
committed
Fix mobile UI and shortcut button functions
1 parent 288ff81 commit 1960f1a

4 files changed

Lines changed: 153 additions & 16 deletions

File tree

‎package-lock.json‎

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "electerm-android",
3-
"version": "5.0.7",
3+
"version": "5.0.8",
44
"description": "electerm for Android — a free and open-source ssh/sftp/telnet/RDP/VNC/Spice/ftp client, built on the electerm-web codebase with Capacitor.",
55
"main": "src/app/app.js",
66
"type": "module",
@@ -47,7 +47,7 @@
4747
"preferGlobal": true,
4848
"devDependencies": {
4949
"@ant-design/icons": "^6.2.5",
50-
"@electerm/electerm-react": "^5.0.7",
50+
"@electerm/electerm-react": "^5.0.8",
5151
"@electerm/electerm-resource": "2.2.1",
5252
"@fontsource/maple-mono": "^5.2.6",
5353
"@novnc/novnc": "^1.7.0",

‎src/client/web-components/keyboard-accessory-bar.jsx‎

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,12 @@ const charKeys = {
8585

8686
// Button layout. Each entry: { id, label, type }
8787
// type: 'seq' = terminal sequence, 'char' = insert character,
88-
// 'toggle' = modifier toggle, 'done' = dismiss keyboard
88+
// 'toggle' = modifier toggle, 'done' = dismiss keyboard,
89+
// 'hide' = collapse the bar (keep the keyboard)
90+
//
91+
// NOTE: the 'hide' button is intentionally NOT in this list. It is rendered
92+
// outside the scroll container (see render()) so it stays pinned to the left
93+
// even when the user scrolls the key row all the way to the end.
8994
const buttonLayout = [
9095
{ id: 'esc', label: 'ESC', type: 'seq' },
9196
{ id: 'tab', label: 'TAB', type: 'seq' },
@@ -124,6 +129,7 @@ export default class KeyboardAccessoryBar extends Component {
124129
super(props)
125130
this.state = {
126131
visible: false,
132+
hidden: false,
127133
isTerminal: false,
128134
ctrlActive: false,
129135
altActive: false
@@ -137,13 +143,32 @@ export default class KeyboardAccessoryBar extends Component {
137143
// Use focusin/focusout (bubbling) to detect when any input is focused.
138144
document.addEventListener('focusin', this.handleFocusIn, true)
139145
document.addEventListener('focusout', this.handleFocusOut, true)
146+
this._syncBarHeight()
147+
}
148+
149+
componentDidUpdate () {
150+
this._syncBarHeight()
140151
}
141152

142153
componentWillUnmount () {
143154
document.removeEventListener('focusin', this.handleFocusIn, true)
144155
document.removeEventListener('focusout', this.handleFocusOut, true)
145156
clearTimeout(this._focusTimer)
146157
this._restoreSendData()
158+
// Give the reserved space back.
159+
document.documentElement.style.setProperty('--kb-bar-h', '0px')
160+
}
161+
162+
/**
163+
* Publish the bar's height as --kb-bar-h on :root so the page can reserve
164+
* that much space at the bottom and the bar never covers content (see the
165+
* matching rules in keyboard-accessory-bar.styl). 0 when hidden/collapsed.
166+
*/
167+
_syncBarHeight = () => {
168+
const h = (this.state.visible && !this.state.hidden && this._barEl)
169+
? this._barEl.offsetHeight
170+
: 0
171+
document.documentElement.style.setProperty('--kb-bar-h', h + 'px')
147172
}
148173

149174
handleFocusIn = (e) => {
@@ -159,7 +184,7 @@ export default class KeyboardAccessoryBar extends Component {
159184
// Small delay so focusout→focusin rapid switching (e.g. tapping a bar
160185
// button) doesn't cause a hide→show flicker.
161186
this._focusTimer = setTimeout(() => {
162-
this.setState({ visible: true, isTerminal })
187+
this.setState({ visible: true, hidden: false, isTerminal })
163188
}, 50)
164189
}
165190

@@ -173,6 +198,7 @@ export default class KeyboardAccessoryBar extends Component {
173198
if (!isInput) {
174199
this.setState({
175200
visible: false,
201+
hidden: false,
176202
ctrlActive: false,
177203
altActive: false
178204
})
@@ -318,11 +344,18 @@ export default class KeyboardAccessoryBar extends Component {
318344
const { isTerminal } = this.state
319345

320346
switch (btn.type) {
347+
case 'hide': {
348+
// Collapse the bar but keep the keyboard open so the user can still
349+
// type. A floating button (rendered in render()) brings it back.
350+
this.setState({ hidden: true, ctrlActive: false, altActive: false })
351+
this._restoreSendData()
352+
break
353+
}
321354
case 'done': {
322355
// Dismiss the keyboard by blurring the focused element.
323356
const el = document.activeElement
324357
if (el && el.blur) el.blur()
325-
this.setState({ visible: false, ctrlActive: false, altActive: false })
358+
this.setState({ visible: false, hidden: false, ctrlActive: false, altActive: false })
326359
this._restoreSendData()
327360
break
328361
}
@@ -382,6 +415,7 @@ export default class KeyboardAccessoryBar extends Component {
382415
const active = btn.id === 'ctrl' ? ctrlActive : altActive
383416
cls += active ? ' kb-key-active' : ''
384417
}
418+
if (btn.id === 'hide') cls += ' kb-key-hide'
385419
if (btn.id === 'done') cls += ' kb-key-done'
386420
if (btn.id === 'backspace') cls += ' kb-key-wide'
387421
if (btn.id === 'enter') cls += ' kb-key-enter'
@@ -401,12 +435,46 @@ export default class KeyboardAccessoryBar extends Component {
401435
}
402436

403437
render () {
404-
if (!this.state.visible) return null
438+
const { visible, hidden } = this.state
439+
if (!visible) return null
440+
// Collapsed: render just a small floating button to bring the bar back.
441+
// The keyboard stays open; only the bar is hidden.
442+
if (hidden) {
443+
return (
444+
<button
445+
className='kb-accessory-show'
446+
aria-label='Show keyboard toolbar'
447+
// Keep focus on the terminal/input underneath.
448+
onMouseDown={(e) => e.preventDefault()}
449+
onTouchStart={(e) => e.preventDefault()}
450+
onClick={() => this.setState({ hidden: false })}
451+
type='button'
452+
>
453+
▴
454+
</button>
455+
)
456+
}
405457
const { isTerminal } = this.state
406458
return (
407-
<div className='kb-accessory-bar'>
408-
<div className='kb-accessory-scroll'>
409-
{buttonLayout.map(btn => this.renderButton(btn))}
459+
<div
460+
className='kb-accessory-bar'
461+
ref={(el) => { this._barEl = el }}
462+
>
463+
<div className='kb-accessory-row'>
464+
{/* Pinned hide button — always visible at the left, never scrolls. */}
465+
<button
466+
className='kb-key kb-key-hide'
467+
aria-label='Hide keyboard toolbar'
468+
onMouseDown={(e) => e.preventDefault()}
469+
onTouchStart={(e) => e.preventDefault()}
470+
onClick={(e) => this.handleButton({ id: 'hide', type: 'hide' }, e)}
471+
type='button'
472+
>
473+
▾
474+
</button>
475+
<div className='kb-accessory-scroll'>
476+
{buttonLayout.map(btn => this.renderButton(btn))}
477+
</div>
410478
</div>
411479
{!isTerminal && (
412480
<div className='kb-accessory-hint'>

‎src/client/web-components/keyboard-accessory-bar.styl‎

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,19 @@
1818
-webkit-user-select none
1919
-webkit-tap-highlight-color transparent
2020

21+
// Row that holds the pinned hide button + the horizontally scrolling keys.
22+
.kb-accessory-row
23+
display flex
24+
align-items center
25+
gap 6px
26+
padding 4px 6px
27+
2128
.kb-accessory-scroll
29+
flex 1 1 auto
30+
min-width 0
2231
display flex
2332
align-items center
2433
gap 2px
25-
padding 4px 6px
2634
overflow-x auto
2735
overflow-y hidden
2836
scrollbar-width none
@@ -69,6 +77,40 @@
6977
&:active
7078
opacity 0.7
7179

80+
// Hide (collapse) button — pinned to the left of the bar, outside the scroll
81+
// area, so it is always reachable even when the key row is scrolled to the end.
82+
.kb-key-hide
83+
flex-shrink 0
84+
font-size 15px
85+
opacity 0.8
86+
&:active
87+
opacity 0.55
88+
89+
// Floating "bring the bar back" button shown while the bar is collapsed.
90+
.kb-accessory-show
91+
position fixed
92+
right 10px
93+
bottom 10px
94+
z-index 9998
95+
width 36px
96+
height 36px
97+
border-radius 50%
98+
border 1px solid var(--border-color, #3a3d42)
99+
background var(--primary, #4a90d9)
100+
color #fff
101+
font-size 16px
102+
line-height 1
103+
display flex
104+
align-items center
105+
justify-content center
106+
box-shadow 0 2px 8px rgba(0, 0, 0, 0.3)
107+
cursor pointer
108+
user-select none
109+
-webkit-user-select none
110+
-webkit-tap-highlight-color transparent
111+
&:active
112+
opacity 0.7
113+
72114
.kb-key-wide
73115
min-width 48px
74116

@@ -80,3 +122,30 @@
80122
font-size 10px
81123
color var(--text-light, #8b909f)
82124
text-align center
125+
126+
// ---------------------------------------------------------------------------
127+
// Reserve space so the accessory bar never covers page content.
128+
//
129+
// The bar publishes its live height as --kb-bar-h (in px) on :root via JS
130+
// (KeyboardAccessoryBar._syncBarHeight). It is set to the bar's pixel height
131+
// while the bar is visible & expanded, and 0 otherwise — so these rules are
132+
// no-ops unless the bar is actually on screen.
133+
:root
134+
--kb-bar-h 0px
135+
136+
.is-mobile
137+
// Lift the footer (which carries the batch input) so its own input is not
138+
// hidden behind the bar. .main-footer is position:absolute; bottom:0.
139+
.main-footer
140+
bottom var(--kb-bar-h, 0px)
141+
// Shrink the terminal area by the bar height so the last terminal rows are
142+
// not covered. border-box keeps .term-wrap inside its layout cell; xterm's
143+
// FitAddon re-measures the (now shorter) .xterm and drops rows accordingly.
144+
.term-wrap
145+
box-sizing border-box
146+
padding-bottom var(--kb-bar-h, 0px)
147+
// Fullscreen terminal: term-wrap-1 is the inset box (bottom:10px).
148+
&.fullscreen
149+
.session-wrap.session-current
150+
.term-wrap-1
151+
bottom calc(10px + var(--kb-bar-h, 0px)) !important

0 commit comments

Comments
 (0)