Skip to content

Commit 9a09e96

Browse files
Fix mobile keyboard
1 parent 9bf1760 commit 9a09e96

2 files changed

Lines changed: 121 additions & 88 deletions

File tree

docs/app.js

Lines changed: 109 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -38,56 +38,50 @@ class UConsole {
3838
// ==================== Init Listeners ====================
3939

4040
_initAllListeners() {
41-
// Connect / Disconnect
42-
document.getElementById('btnConnect').addEventListener('click', () => this._toggleConnection());
43-
document.getElementById('btnRefreshPorts').addEventListener('click', () => this._refreshPorts());
44-
45-
// Send
46-
document.getElementById('btnSend').addEventListener('click', () => this._sendFromInput());
47-
document.getElementById('sendInput').addEventListener('keydown', (e) => {
48-
if (e.key === 'Enter' && !e.ctrlKey && !e.shiftKey) {
49-
e.preventDefault();
50-
this._sendFromInput();
51-
}
52-
if (e.key === 'Enter' && e.ctrlKey) {
53-
e.preventDefault();
54-
this._sendCRLF();
55-
}
56-
});
57-
58-
// CR, LF, CRLF
59-
document.getElementById('btnCR').addEventListener('click', () => this._sendControl(0x0D, '<CR>'));
60-
document.getElementById('btnLF').addEventListener('click', () => this._sendControl(0x0A, '<LF>'));
61-
document.getElementById('btnCRLF').addEventListener('click', () => this._sendCRLF());
62-
63-
// NMEA Checksum
64-
document.getElementById('btnNMEAChecksum').addEventListener('click', () => this._addNMEAChecksum());
65-
66-
// Record Binary
67-
document.getElementById('btnRecordBinary').addEventListener('click', () => this._toggleRecording());
68-
69-
// Ping-Pong
70-
document.getElementById('pingPongCb').addEventListener('change', (e) => {
71-
this.isPingPong = e.target.checked;
72-
});
73-
74-
// Display mode handled in _initDisplayModeSwitch
41+
// Connect / Disconnect
42+
document.getElementById('btnConnect').addEventListener('click', () => this._toggleConnection());
43+
document.getElementById('btnRefreshPorts').addEventListener('click', () => this._refreshPorts());
44+
45+
// Send
46+
document.getElementById('btnSend').addEventListener('click', () => this._sendFromInput());
47+
document.getElementById('sendInput').addEventListener('keydown', (e) => {
48+
if (e.key === 'Enter' && !e.ctrlKey && !e.shiftKey) {
49+
e.preventDefault();
50+
this._sendFromInput();
51+
}
52+
if (e.key === 'Enter' && e.ctrlKey) {
53+
e.preventDefault();
54+
this._sendCRLF();
55+
}
56+
});
7557

76-
// Autoscroll
77-
document.getElementById('autoscrollCb').addEventListener('change', () => {
78-
if (document.getElementById('autoscrollCb').checked) {
79-
this._scrollToBottom();
80-
}
81-
});
58+
// CR, LF, CRLF
59+
document.getElementById('btnCR').addEventListener('click', () => this._sendControl(0x0D, '<CR>'));
60+
document.getElementById('btnLF').addEventListener('click', () => this._sendControl(0x0A, '<LF>'));
61+
document.getElementById('btnCRLF').addEventListener('click', () => this._sendCRLF());
8262

83-
// Copy / Clear / Save
84-
document.getElementById('btnCopy').addEventListener('click', () => this._copyHistory());
85-
document.getElementById('btnClear').addEventListener('click', () => this._clearHistory());
86-
document.getElementById('btnSaveText').addEventListener('click', () => this._saveHistoryText());
63+
// NMEA Checksum
64+
document.getElementById('btnNMEAChecksum').addEventListener('click', () => this._addNMEAChecksum());
8765

66+
// Record Binary
67+
document.getElementById('btnRecordBinary').addEventListener('click', () => this._toggleRecording());
8868

69+
// Ping-Pong
70+
document.getElementById('pingPongCb').addEventListener('change', (e) => {
71+
this.isPingPong = e.target.checked;
72+
});
8973

74+
// Autoscroll
75+
document.getElementById('autoscrollCb').addEventListener('change', () => {
76+
if (document.getElementById('autoscrollCb').checked) {
77+
this._scrollToBottom();
78+
}
79+
});
9080

81+
// Copy / Clear / Save
82+
document.getElementById('btnCopy').addEventListener('click', () => this._copyHistory());
83+
document.getElementById('btnClear').addEventListener('click', () => this._clearHistory());
84+
document.getElementById('btnSaveText').addEventListener('click', () => this._saveHistoryText());
9185

9286
// ================= Dropdown Generate =================
9387
const setupDropdown = (toggleId, menuId, isSend) => {
@@ -152,49 +146,81 @@ class UConsole {
152146
});
153147
});
154148

155-
156-
157-
158-
// Фикс клавиатуры на мобильных
159-
const sendInput = document.getElementById('sendInput');
160-
sendInput.addEventListener('focus', () => {
161-
setTimeout(() => {
162-
sendInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
163-
// Дополнительно скроллим историю чтобы место освободить
164-
const history = document.getElementById('historyContainer');
165-
if (history.scrollHeight > history.clientHeight) {
166-
history.scrollTop = history.scrollHeight;
149+
// ================= Фикс клавиатуры на мобильных =================
150+
if (window.visualViewport) {
151+
const sendInput = document.getElementById('sendInput');
152+
const historyContainer = document.getElementById('historyContainer');
153+
const sendPanel = document.querySelector('.send-panel');
154+
let originalHistoryHeight = null;
155+
156+
const adjustForKeyboard = () => {
157+
const viewport = window.visualViewport;
158+
const keyboardHeight = window.innerHeight - viewport.height;
159+
const inputFocused = document.activeElement === sendInput;
160+
161+
if (keyboardHeight > 100 && inputFocused) {
162+
// Сохраняем изначальную высоту истории
163+
if (!originalHistoryHeight) {
164+
originalHistoryHeight = historyContainer.style.maxHeight || '400px';
165+
}
166+
167+
// Уменьшаем историю, освобождая место
168+
const availableHeight = viewport.height - sendPanel.offsetHeight - 120;
169+
historyContainer.style.maxHeight = `${Math.max(150, availableHeight)}px`;
170+
historyContainer.style.transition = 'max-height 0.2s ease-out';
171+
172+
// Прокручиваем к полю ввода
173+
requestAnimationFrame(() => {
174+
const inputRect = sendInput.getBoundingClientRect();
175+
if (inputRect.bottom > viewport.height - 20) {
176+
sendInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
177+
}
178+
});
179+
} else if (!inputFocused && keyboardHeight < 50) {
180+
// Клавиатура закрыта — возвращаем как было
181+
historyContainer.style.maxHeight = originalHistoryHeight || '';
182+
originalHistoryHeight = null;
167183
}
168-
}, 300); // Задержка под открытие клавиатуры
169-
});
170-
171-
// На мобильных при потере фокуса скроллим обратно
172-
sendInput.addEventListener('blur', () => {
173-
setTimeout(() => {
174-
window.scrollTo({ top: 0, behavior: 'smooth' });
175-
}, 100);
176-
});
177-
178-
179-
184+
};
185+
186+
window.visualViewport.addEventListener('resize', adjustForKeyboard);
187+
window.visualViewport.addEventListener('scroll', adjustForKeyboard);
188+
189+
// Фокус — сразу адаптируем
190+
sendInput.addEventListener('focus', () => {
191+
setTimeout(adjustForKeyboard, 250); // Ждём открытия клавиатуры
192+
});
193+
194+
// Потеря фокуса — возвращаем через задержку
195+
sendInput.addEventListener('blur', () => {
196+
setTimeout(() => {
197+
if (document.activeElement !== sendInput) {
198+
historyContainer.style.maxHeight = originalHistoryHeight || '';
199+
historyContainer.style.transition = 'max-height 0.3s ease-out';
200+
originalHistoryHeight = null;
201+
window.scrollTo({ top: 0, behavior: 'smooth' });
202+
}
203+
}, 500);
204+
});
205+
}
180206

181-
// New instance links
182-
const newInstanceHandler = (e) => {
183-
e.preventDefault();
184-
window.open(window.location.href, '_blank');
185-
};
186-
document.getElementById('newInstanceLink').addEventListener('click', newInstanceHandler);
187-
document.getElementById('footerNewInstance').addEventListener('click', newInstanceHandler);
207+
// New instance links
208+
const newInstanceHandler = (e) => {
209+
e.preventDefault();
210+
window.open(window.location.href, '_blank');
211+
};
212+
document.getElementById('newInstanceLink').addEventListener('click', newInstanceHandler);
213+
document.getElementById('footerNewInstance').addEventListener('click', newInstanceHandler);
188214

189-
// Language selector
190-
document.getElementById('langSelector').addEventListener('change', (e) => {
191-
I18n.setLanguage(e.target.value);
192-
this._updateConnectionStatus();
193-
});
215+
// Language selector
216+
document.getElementById('langSelector').addEventListener('change', (e) => {
217+
I18n.setLanguage(e.target.value);
218+
this._updateConnectionStatus();
219+
});
194220

195-
// Refresh ports on first load
196-
this._refreshPorts();
197-
}
221+
// Refresh ports on first load
222+
this._refreshPorts();
223+
}
198224

199225
_initDisplayModeSwitch() {
200226
const radios = document.querySelectorAll('input[name="displayMode"]');

docs/style.css

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -246,21 +246,28 @@ select.form-control {
246246

247247
/* ================= Фикс клавиатуры на мобильных ================= */
248248
html {
249-
height: 100%;
250-
/* Используем dvh для динамической высоты viewport */
249+
height: 100vh;
251250
height: 100dvh;
252251
}
253252

254253
body {
255-
height: 100%;
254+
height: 100vh;
256255
height: 100dvh;
257256
overflow-y: auto;
258257
-webkit-overflow-scrolling: touch;
259258
}
260259

261-
/* При фокусе на input прокручиваем так, чтобы было видно */
260+
/* Резервный отступ на случай если JS не сработал */
262261
#sendInput:focus {
263-
scroll-margin-bottom: 50vh; /* Оставляет место для клавиатуры */
262+
scroll-margin-bottom: 40vh;
263+
}
264+
265+
/* На мобильных панель ввода чуть приподнята */
266+
@media (max-width: 600px) {
267+
.send-panel {
268+
position: relative;
269+
z-index: 10;
270+
}
264271
}
265272

266273
/* Toolbar */

0 commit comments

Comments
 (0)