diff --git a/src/textual_webterm/static/js/terminal.js b/src/textual_webterm/static/js/terminal.js
index 3ee9616..748067d 100644
--- a/src/textual_webterm/static/js/terminal.js
+++ b/src/textual_webterm/static/js/terminal.js
@@ -25,13 +25,14 @@ var c=Object.defineProperty;var C=(V,Z)=>{for(var $ in Z)c(V,$,{get:Z[$],enumera
+
`;let Z=document.createElement("style");Z.textContent=`
.mobile-keybar {
position: fixed;
bottom: 80px;
right: 0;
- display: flex;
- flex-wrap: wrap;
+ display: grid;
+ grid-template-columns: repeat(5, auto);
gap: 4px;
padding: 6px;
background: rgba(40, 40, 40, 0.95);
@@ -39,7 +40,6 @@ var c=Object.defineProperty;var C=(V,Z)=>{for(var $ in Z)c(V,$,{get:Z[$],enumera
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
z-index: 10000;
touch-action: none;
- max-width: 200px;
user-select: none;
-webkit-user-select: none;
}
@@ -72,4 +72,8 @@ var c=Object.defineProperty;var C=(V,Z)=>{for(var $ in Z)c(V,$,{get:Z[$],enumera
.mobile-keybar .keybar-drag:active {
cursor: grabbing;
}
+ .mobile-keybar .keybar-return {
+ grid-column: 5;
+ grid-row: 2;
+ }
`,document.head.appendChild(Z),document.body.appendChild(V),this.mobileKeybar=V,V.querySelectorAll("button[data-key]").forEach(($)=>{$.addEventListener("touchstart",(O)=>{O.preventDefault();let P=$.dataset.key||"";if(P=P.replace(/\\x([0-9a-fA-F]{2})/g,(X,K)=>String.fromCharCode(parseInt(K,16))),P=P.replace(/\\x1b/g,"\x1B"),this.shiftActive&&P==="\t")P="\x1B[Z";else if(this.shiftActive&&P.startsWith("\x1B[")&&P.length===3)P=`\x1B[1;2${P[2]}`;else if(this.ctrlActive&&P.startsWith("\x1B[")&&P.length===3)P=`\x1B[1;5${P[2]}`;else if(this.ctrlActive&&this.shiftActive&&P.startsWith("\x1B[")&&P.length===3)P=`\x1B[1;6${P[2]}`;else if(this.ctrlActive&&P.length===1){let X=P.toUpperCase().charCodeAt(0);if(X>=65&&X<=90)P=String.fromCharCode(X-64)}this.send(["stdin",P]),this.deactivateModifiers()})}),V.querySelectorAll("button[data-modifier]").forEach(($)=>{$.addEventListener("touchstart",(O)=>{O.preventDefault();let P=$.dataset.modifier;if(P==="ctrl")this.ctrlActive=!this.ctrlActive,$.classList.toggle("active",this.ctrlActive);else if(P==="shift")this.shiftActive=!this.shiftActive,$.classList.toggle("active",this.shiftActive)})}),this.setupKeybarDrag(V)}setupKeybarDrag(V){let Z=V.querySelector(".keybar-drag");if(!Z)return;let $=!1,O=0,P=0,X=0,K=0,_=(L)=>{if(L.touches.length!==1)return;$=!0;let S=L.touches[0];O=S.clientX,P=S.clientY;let J=V.getBoundingClientRect();X=window.innerWidth-J.right,K=window.innerHeight-J.bottom,L.preventDefault()},Y=(L)=>{if(!$||L.touches.length!==1)return;let S=L.touches[0],J=O-S.clientX,u=P-S.clientY,G=Math.max(0,Math.min(window.innerWidth-100,X+J)),W=Math.max(0,Math.min(window.innerHeight-50,K+u));V.style.right=`${G}px`,V.style.bottom=`${W}px`,L.preventDefault()},R=()=>{$=!1};Z.addEventListener("touchstart",_,{passive:!1}),document.addEventListener("touchmove",Y,{passive:!1}),document.addEventListener("touchend",R)}deactivateModifiers(){this.ctrlActive=!1,this.shiftActive=!1,this.mobileKeybar?.querySelectorAll("button[data-modifier]").forEach((V)=>{V.classList.remove("active")})}focusMobileInput(){this.mobileInput?.focus()}async waitForFonts(){if(!("fonts"in document))return;try{await document.fonts.ready}catch{}}fit(){let Z=this.terminal.renderer,$,O;if(Z?.getMetrics){let u=Z.getMetrics();if(u&&u.width>0&&u.height>0)$=u.width,O=u.height;else{let G=this.measureCellSize();if(!G){this.fitAddon.fit();return}$=G.width,O=G.height}}else{let u=this.measureCellSize();if(!u){this.fitAddon.fit();return}$=u.width,O=u.height}let P=window.getComputedStyle(this.element),X=parseInt(P.paddingTop)||0,K=parseInt(P.paddingBottom)||0,_=parseInt(P.paddingLeft)||0,Y=parseInt(P.paddingRight)||0,R=this.element.clientWidth-_-Y,L=this.element.clientHeight-X-K;if(R<=0||L<=0)return;let S=Math.max(2,Math.floor(R/$)),J=Math.max(1,Math.floor(L/O));if(S!==this.terminal.cols||J!==this.terminal.rows)this.terminal.resize(S,J)}measureCellSize(){let V=document.createElement("span");V.style.visibility="hidden",V.style.position="absolute",V.style.fontFamily=this.options.fontFamily||"monospace",V.style.fontSize=`${this.options.fontSize||14}px`,V.style.lineHeight="normal",V.textContent="W",document.body.appendChild(V);let{offsetWidth:Z,offsetHeight:$}=V;if(document.body.removeChild(V),Z>0&&$>0)return{width:Z,height:$};return null}setupResizeObserver(){new ResizeObserver(()=>{if(this.resizeDebounceTimer)clearTimeout(this.resizeDebounceTimer);this.resizeDebounceTimer=window.setTimeout(()=>{this.fit()},100)}).observe(this.element)}resizeDebounceTimer;isValidSize(V,Z){return V>=2&&V<=500&&Z>=1&&Z<=200}connect(){if(this.socket?.readyState===WebSocket.OPEN)return;this.socket=new WebSocket(this.wsUrl),this.socket.binaryType="arraybuffer",this.socket.addEventListener("open",()=>{this.reconnectAttempts=0,this.element.classList.add("-connected"),this.element.classList.remove("-disconnected"),this.processMessageQueue();let V=this.terminal.cols,Z=this.terminal.rows;if(this.isValidSize(V,Z))this.lastValidSize={cols:V,rows:Z},this.send(["resize",{width:V,height:Z}]);this.terminal.focus()}),this.socket.addEventListener("close",()=>{this.element.classList.remove("-connected"),this.element.classList.add("-disconnected"),this.scheduleReconnect()}),this.socket.addEventListener("error",()=>{}),this.socket.addEventListener("message",(V)=>{this.handleMessage(V.data)})}handleMessage(V){if(V instanceof ArrayBuffer){let Z=new TextDecoder().decode(V);this.terminal.write(Z);return}try{let Z=JSON.parse(V),[$,O]=Z;switch($){case"stdout":this.terminal.write(O);break;case"pong":break;default:console.debug("Unknown message type:",$)}}catch{this.terminal.write(V)}}send(V){this.messageQueue.push(V),this.processMessageQueue()}processMessageQueue(){if(this.socket?.readyState!==WebSocket.OPEN)return;while(this.messageQueue.length>0){let V=this.messageQueue.shift();try{if(V)this.socket.send(JSON.stringify(V))}catch(Z){if(console.error("Failed to send message:",Z,V),V)this.messageQueue.unshift(V);break}}}scheduleReconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts){console.error("Max reconnection attempts reached");return}this.reconnectAttempts++;let V=this.reconnectDelay*Math.pow(2,this.reconnectAttempts-1);setTimeout(()=>{console.log(`Reconnecting (attempt ${this.reconnectAttempts})...`),this.connect()},V)}dispose(){if(this.socket?.close(),this.mobileInput)this.mobileInput.remove(),this.mobileInput=null;if(this.mobileKeybar)this.mobileKeybar.remove(),this.mobileKeybar=null;this.fitAddon.dispose(),this.terminal.dispose()}}var Q=new Map;async function I(){let V=document.querySelectorAll(".textual-terminal");for(let Z of V){let $=Z.dataset.sessionWebsocketUrl;if(!$){console.error("Missing data-session-websocket-url on terminal container");continue}let O=a(Z);try{let P=await x.create(Z,$,O);Q.set(Z,P)}catch(P){console.error("Failed to create terminal:",P)}}}if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>I());else I();export{Q as instances,I as initTerminals,x as WebTerminal,f as THEMES};
diff --git a/src/textual_webterm/static/js/terminal.ts b/src/textual_webterm/static/js/terminal.ts
index 1d6ddae..7a791e5 100644
--- a/src/textual_webterm/static/js/terminal.ts
+++ b/src/textual_webterm/static/js/terminal.ts
@@ -557,6 +557,7 @@ class WebTerminal {
+
`;
// Inject styles
@@ -566,8 +567,8 @@ class WebTerminal {
position: fixed;
bottom: 80px;
right: 0;
- display: flex;
- flex-wrap: wrap;
+ display: grid;
+ grid-template-columns: repeat(5, auto);
gap: 4px;
padding: 6px;
background: rgba(40, 40, 40, 0.95);
@@ -575,7 +576,6 @@ class WebTerminal {
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
z-index: 10000;
touch-action: none;
- max-width: 200px;
user-select: none;
-webkit-user-select: none;
}
@@ -608,6 +608,10 @@ class WebTerminal {
.mobile-keybar .keybar-drag:active {
cursor: grabbing;
}
+ .mobile-keybar .keybar-return {
+ grid-column: 5;
+ grid-row: 2;
+ }
`;
document.head.appendChild(style);
document.body.appendChild(keybar);