Fix on-screen keyboard clearing fields on frameworks like Authelia's React UI

The keyboard-type IPC handler set input.value directly and dispatched a
plain "input" event. React (and similar frameworks) patch the value
setter on input/textarea instances to track the last value they set;
setting el.value directly updates that tracker too, so React never
detects a real change and its controlled state stays empty. The next
re-render (moving focus to another field, toggling a checkbox, etc.)
then redraws the input from that stale empty state, wiping out
whatever was typed. Route the writes through the native value setter
instead so the tracker stays out of sync and the dispatched event
actually reaches the framework's handler.

Also give the "Tab" key its own handler that moves focus to the next
focusable element, instead of falling through to the generic branch
that typed the literal word "Tab" into the field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1D2cv1KyFoYGnwSdszeXm
This commit is contained in:
Claude
2026-09-08 00:54:01 +00:00
parent ff67e422a4
commit d6d6177dd9
+47 -6
View File
@@ -1492,17 +1492,37 @@ async function createWindow(){
} }
if(!view||!view.webContents)return; if(!view||!view.webContents)return;
const safeKey=JSON.stringify(key).slice(1,-1); const safeKey=JSON.stringify(key).slice(1,-1);
// Sets the value through the native <input>/<textarea> value setter
// instead of the instance property. Frameworks like React override the
// instance setter to track the "last known value"; setting el.value
// directly also updates that tracker, so the framework never sees a
// real change and its own controlled state stays empty. On the next
// re-render (focusing a different field, or any unrelated state change
// such as toggling a checkbox) it redraws the input from that stale
// empty state, which is why typed text appears to vanish. Going through
// the native setter keeps the tracker out of sync so the dispatched
// "input" event actually reaches the framework's handler.
const setNativeValue=`
function __kioskSetValue(el,value){
const proto=el.tagName==="TEXTAREA"?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;
const setter=Object.getOwnPropertyDescriptor(proto,"value").set;
setter.call(el,value);
}
`;
if(key==='Backspace'){ if(key==='Backspace'){
view.webContents.executeJavaScript(` view.webContents.executeJavaScript(`
(function(){ (function(){
${setNativeValue}
const el=document.activeElement; const el=document.activeElement;
if(el&&(el.tagName==="INPUT"||el.tagName==="TEXTAREA")){ if(el&&(el.tagName==="INPUT"||el.tagName==="TEXTAREA")){
const s=el.selectionStart||0; const s=el.selectionStart||0;
if(s>0){ if(s>0){
el.value=el.value.substring(0,s-1)+el.value.substring(el.selectionEnd||s); const newValue=el.value.substring(0,s-1)+el.value.substring(el.selectionEnd||s);
__kioskSetValue(el,newValue);
el.selectionStart=el.selectionEnd=s-1; el.selectionStart=el.selectionEnd=s-1;
el.dispatchEvent(new Event("input",{bubbles:true})); el.dispatchEvent(new Event("input",{bubbles:true}));
} }
@@ -1512,11 +1532,13 @@ async function createWindow(){
}else if(key==='Enter'){ }else if(key==='Enter'){
view.webContents.executeJavaScript(` view.webContents.executeJavaScript(`
(function(){ (function(){
${setNativeValue}
const el=document.activeElement; const el=document.activeElement;
if(el){ if(el){
if(el.tagName==="TEXTAREA"){ if(el.tagName==="TEXTAREA"){
const s=el.selectionStart||0; const s=el.selectionStart||0;
el.value=el.value.substring(0,s)+"\\n"+el.value.substring(el.selectionEnd||s); const newValue=el.value.substring(0,s)+"\\n"+el.value.substring(el.selectionEnd||s);
__kioskSetValue(el,newValue);
el.selectionStart=el.selectionEnd=s+1; el.selectionStart=el.selectionEnd=s+1;
el.dispatchEvent(new Event("input",{bubbles:true})); el.dispatchEvent(new Event("input",{bubbles:true}));
}else if(el.tagName==="INPUT"){ }else if(el.tagName==="INPUT"){
@@ -1528,13 +1550,30 @@ async function createWindow(){
} }
})(); })();
`).catch(()=>{}); `).catch(()=>{});
}else if(key==='Tab'){
view.webContents.executeJavaScript(`
(function(){
const selector='input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled]), button:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])';
const focusable=Array.prototype.filter.call(
document.querySelectorAll(selector),
el=>el.offsetParent!==null
);
if(focusable.length>0){
const idx=focusable.indexOf(document.activeElement);
const next=idx>=0?focusable[(idx+1)%focusable.length]:focusable[0];
next.focus();
}
})();
`).catch(()=>{});
}else if(key===' '){ }else if(key===' '){
view.webContents.executeJavaScript(` view.webContents.executeJavaScript(`
(function(){ (function(){
${setNativeValue}
const el=document.activeElement; const el=document.activeElement;
if(el&&(el.tagName==="INPUT"||el.tagName==="TEXTAREA")){ if(el&&(el.tagName==="INPUT"||el.tagName==="TEXTAREA")){
const s=el.selectionStart||0; const s=el.selectionStart||0;
el.value=el.value.substring(0,s)+" "+el.value.substring(el.selectionEnd||s); const newValue=el.value.substring(0,s)+" "+el.value.substring(el.selectionEnd||s);
__kioskSetValue(el,newValue);
el.selectionStart=el.selectionEnd=s+1; el.selectionStart=el.selectionEnd=s+1;
el.dispatchEvent(new Event("input",{bubbles:true})); el.dispatchEvent(new Event("input",{bubbles:true}));
} }
@@ -1546,12 +1585,14 @@ async function createWindow(){
}else{ }else{
view.webContents.executeJavaScript(` view.webContents.executeJavaScript(`
(function(){ (function(){
${setNativeValue}
const text="${safeKey}"; const text="${safeKey}";
const el=document.activeElement; const el=document.activeElement;
if(el&&(el.tagName==="INPUT"||el.tagName==="TEXTAREA")){ if(el&&(el.tagName==="INPUT"||el.tagName==="TEXTAREA")){
const s=el.selectionStart||0; const s=el.selectionStart||0;
const e=el.selectionEnd||s; const e=el.selectionEnd||s;
el.value=el.value.substring(0,s)+text+el.value.substring(e); const newValue=el.value.substring(0,s)+text+el.value.substring(e);
__kioskSetValue(el,newValue);
el.selectionStart=el.selectionEnd=s+text.length; el.selectionStart=el.selectionEnd=s+text.length;
el.dispatchEvent(new Event("input",{bubbles:true})); el.dispatchEvent(new Event("input",{bubbles:true}));
el.dispatchEvent(new Event("change",{bubbles:true})); el.dispatchEvent(new Event("change",{bubbles:true}));