@@ -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"]' ) ;
0 commit comments